@buenojs/bueno 0.8.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/.env.example +109 -0
- package/.github/workflows/ci.yml +31 -0
- package/LICENSE +21 -0
- package/README.md +892 -0
- package/architecture.md +652 -0
- package/bun.lock +70 -0
- package/dist/cli/index.js +3233 -0
- package/dist/index.js +9014 -0
- package/package.json +77 -0
- package/src/cache/index.ts +795 -0
- package/src/cli/ARCHITECTURE.md +837 -0
- package/src/cli/bin.ts +10 -0
- package/src/cli/commands/build.ts +425 -0
- package/src/cli/commands/dev.ts +248 -0
- package/src/cli/commands/generate.ts +541 -0
- package/src/cli/commands/help.ts +55 -0
- package/src/cli/commands/index.ts +112 -0
- package/src/cli/commands/migration.ts +355 -0
- package/src/cli/commands/new.ts +804 -0
- package/src/cli/commands/start.ts +208 -0
- package/src/cli/core/args.ts +283 -0
- package/src/cli/core/console.ts +349 -0
- package/src/cli/core/index.ts +60 -0
- package/src/cli/core/prompt.ts +424 -0
- package/src/cli/core/spinner.ts +265 -0
- package/src/cli/index.ts +135 -0
- package/src/cli/templates/deploy.ts +295 -0
- package/src/cli/templates/docker.ts +307 -0
- package/src/cli/templates/index.ts +24 -0
- package/src/cli/utils/fs.ts +428 -0
- package/src/cli/utils/index.ts +8 -0
- package/src/cli/utils/strings.ts +197 -0
- package/src/config/env.ts +408 -0
- package/src/config/index.ts +506 -0
- package/src/config/loader.ts +329 -0
- package/src/config/merge.ts +285 -0
- package/src/config/types.ts +320 -0
- package/src/config/validation.ts +441 -0
- package/src/container/forward-ref.ts +143 -0
- package/src/container/index.ts +386 -0
- package/src/context/index.ts +360 -0
- package/src/database/index.ts +1142 -0
- package/src/database/migrations/index.ts +371 -0
- package/src/database/schema/index.ts +619 -0
- package/src/frontend/api-routes.ts +640 -0
- package/src/frontend/bundler.ts +643 -0
- package/src/frontend/console-client.ts +419 -0
- package/src/frontend/console-stream.ts +587 -0
- package/src/frontend/dev-server.ts +846 -0
- package/src/frontend/file-router.ts +611 -0
- package/src/frontend/frameworks/index.ts +106 -0
- package/src/frontend/frameworks/react.ts +85 -0
- package/src/frontend/frameworks/solid.ts +104 -0
- package/src/frontend/frameworks/svelte.ts +110 -0
- package/src/frontend/frameworks/vue.ts +92 -0
- package/src/frontend/hmr-client.ts +663 -0
- package/src/frontend/hmr.ts +728 -0
- package/src/frontend/index.ts +342 -0
- package/src/frontend/islands.ts +552 -0
- package/src/frontend/isr.ts +555 -0
- package/src/frontend/layout.ts +475 -0
- package/src/frontend/ssr/react.ts +446 -0
- package/src/frontend/ssr/solid.ts +523 -0
- package/src/frontend/ssr/svelte.ts +546 -0
- package/src/frontend/ssr/vue.ts +504 -0
- package/src/frontend/ssr.ts +699 -0
- package/src/frontend/types.ts +2274 -0
- package/src/health/index.ts +604 -0
- package/src/index.ts +410 -0
- package/src/lock/index.ts +587 -0
- package/src/logger/index.ts +444 -0
- package/src/logger/transports/index.ts +969 -0
- package/src/metrics/index.ts +494 -0
- package/src/middleware/built-in.ts +360 -0
- package/src/middleware/index.ts +94 -0
- package/src/modules/filters.ts +458 -0
- package/src/modules/guards.ts +405 -0
- package/src/modules/index.ts +1256 -0
- package/src/modules/interceptors.ts +574 -0
- package/src/modules/lazy.ts +418 -0
- package/src/modules/lifecycle.ts +478 -0
- package/src/modules/metadata.ts +90 -0
- package/src/modules/pipes.ts +626 -0
- package/src/router/index.ts +339 -0
- package/src/router/linear.ts +371 -0
- package/src/router/regex.ts +292 -0
- package/src/router/tree.ts +562 -0
- package/src/rpc/index.ts +1263 -0
- package/src/security/index.ts +436 -0
- package/src/ssg/index.ts +631 -0
- package/src/storage/index.ts +456 -0
- package/src/telemetry/index.ts +1097 -0
- package/src/testing/index.ts +1586 -0
- package/src/types/index.ts +236 -0
- package/src/types/optional-deps.d.ts +219 -0
- package/src/validation/index.ts +276 -0
- package/src/websocket/index.ts +1004 -0
- package/tests/integration/cli.test.ts +1016 -0
- package/tests/integration/fullstack.test.ts +234 -0
- package/tests/unit/cache.test.ts +174 -0
- package/tests/unit/cli-commands.test.ts +892 -0
- package/tests/unit/cli.test.ts +1258 -0
- package/tests/unit/container.test.ts +279 -0
- package/tests/unit/context.test.ts +221 -0
- package/tests/unit/database.test.ts +183 -0
- package/tests/unit/linear-router.test.ts +280 -0
- package/tests/unit/lock.test.ts +336 -0
- package/tests/unit/middleware.test.ts +184 -0
- package/tests/unit/modules.test.ts +142 -0
- package/tests/unit/pubsub.test.ts +257 -0
- package/tests/unit/regex-router.test.ts +265 -0
- package/tests/unit/router.test.ts +373 -0
- package/tests/unit/rpc.test.ts +1248 -0
- package/tests/unit/security.test.ts +174 -0
- package/tests/unit/telemetry.test.ts +371 -0
- package/tests/unit/test-cache.test.ts +110 -0
- package/tests/unit/test-database.test.ts +282 -0
- package/tests/unit/tree-router.test.ts +325 -0
- package/tests/unit/validation.test.ts +794 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in Middleware
|
|
3
|
+
*
|
|
4
|
+
* Common middleware utilities for logging, CORS, request ID, etc.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Context } from "../context";
|
|
8
|
+
import type { Middleware } from "../index";
|
|
9
|
+
|
|
10
|
+
// ============= Logger Middleware =============
|
|
11
|
+
|
|
12
|
+
export interface LoggerOptions {
|
|
13
|
+
format?: "json" | "text";
|
|
14
|
+
level?: "debug" | "info" | "warn" | "error";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function logger(options: LoggerOptions = {}): Middleware {
|
|
18
|
+
const { format = "text", level = "info" } = options;
|
|
19
|
+
|
|
20
|
+
return async (context: Context, next: () => Promise<Response>) => {
|
|
21
|
+
const start = Date.now();
|
|
22
|
+
const { method, path } = context;
|
|
23
|
+
|
|
24
|
+
// Log request
|
|
25
|
+
if (format === "json") {
|
|
26
|
+
console.log(
|
|
27
|
+
JSON.stringify({
|
|
28
|
+
type: "request",
|
|
29
|
+
method,
|
|
30
|
+
path,
|
|
31
|
+
timestamp: new Date().toISOString(),
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
} else {
|
|
35
|
+
console.log(`--> ${method} ${path}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const response = await next();
|
|
40
|
+
const duration = Date.now() - start;
|
|
41
|
+
|
|
42
|
+
// Log response
|
|
43
|
+
if (format === "json") {
|
|
44
|
+
console.log(
|
|
45
|
+
JSON.stringify({
|
|
46
|
+
type: "response",
|
|
47
|
+
method,
|
|
48
|
+
path,
|
|
49
|
+
status: response.status,
|
|
50
|
+
duration,
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
} else {
|
|
55
|
+
console.log(`<-- ${method} ${path} ${response.status} (${duration}ms)`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return response;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
const duration = Date.now() - start;
|
|
61
|
+
|
|
62
|
+
if (format === "json") {
|
|
63
|
+
console.error(
|
|
64
|
+
JSON.stringify({
|
|
65
|
+
type: "error",
|
|
66
|
+
method,
|
|
67
|
+
path,
|
|
68
|
+
error: error instanceof Error ? error.message : String(error),
|
|
69
|
+
duration,
|
|
70
|
+
timestamp: new Date().toISOString(),
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
} else {
|
|
74
|
+
console.error(`<-- ${method} ${path} ERROR (${duration}ms)`, error);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ============= CORS Middleware =============
|
|
83
|
+
|
|
84
|
+
export interface CorsOptions {
|
|
85
|
+
origin?: string | string[] | ((origin: string) => string | undefined);
|
|
86
|
+
methods?: string[];
|
|
87
|
+
allowedHeaders?: string[];
|
|
88
|
+
exposedHeaders?: string[];
|
|
89
|
+
credentials?: boolean;
|
|
90
|
+
maxAge?: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function cors(options: CorsOptions = {}): Middleware {
|
|
94
|
+
const {
|
|
95
|
+
origin = "*",
|
|
96
|
+
methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
97
|
+
allowedHeaders = ["Content-Type", "Authorization"],
|
|
98
|
+
exposedHeaders = [],
|
|
99
|
+
credentials = false,
|
|
100
|
+
maxAge = 86400,
|
|
101
|
+
} = options;
|
|
102
|
+
|
|
103
|
+
return async (context: Context, next: () => Promise<Response>) => {
|
|
104
|
+
const requestOrigin = context.getHeader("origin") ?? "";
|
|
105
|
+
|
|
106
|
+
// Determine allowed origin
|
|
107
|
+
let allowedOrigin: string;
|
|
108
|
+
if (typeof origin === "function") {
|
|
109
|
+
allowedOrigin = origin(requestOrigin) ?? "*";
|
|
110
|
+
} else if (Array.isArray(origin)) {
|
|
111
|
+
allowedOrigin = origin.includes(requestOrigin) ? requestOrigin : "*";
|
|
112
|
+
} else {
|
|
113
|
+
allowedOrigin = origin;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Handle preflight request
|
|
117
|
+
if (context.method === "OPTIONS") {
|
|
118
|
+
return new Response(null, {
|
|
119
|
+
status: 204,
|
|
120
|
+
headers: {
|
|
121
|
+
"Access-Control-Allow-Origin": allowedOrigin,
|
|
122
|
+
"Access-Control-Allow-Methods": methods.join(", "),
|
|
123
|
+
"Access-Control-Allow-Headers": allowedHeaders.join(", "),
|
|
124
|
+
"Access-Control-Allow-Credentials": String(credentials),
|
|
125
|
+
"Access-Control-Max-Age": String(maxAge),
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const response = await next();
|
|
131
|
+
|
|
132
|
+
// Add CORS headers to response
|
|
133
|
+
response.headers.set("Access-Control-Allow-Origin", allowedOrigin);
|
|
134
|
+
response.headers.set(
|
|
135
|
+
"Access-Control-Allow-Credentials",
|
|
136
|
+
String(credentials),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
if (exposedHeaders.length > 0) {
|
|
140
|
+
response.headers.set(
|
|
141
|
+
"Access-Control-Expose-Headers",
|
|
142
|
+
exposedHeaders.join(", "),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return response;
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ============= Request ID Middleware =============
|
|
151
|
+
|
|
152
|
+
export interface RequestIdOptions {
|
|
153
|
+
header?: string;
|
|
154
|
+
generator?: () => string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function generateId(): string {
|
|
158
|
+
return crypto.randomUUID();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function requestId(options: RequestIdOptions = {}): Middleware {
|
|
162
|
+
const { header = "X-Request-Id", generator = generateId } = options;
|
|
163
|
+
|
|
164
|
+
return async (context: Context, next: () => Promise<Response>) => {
|
|
165
|
+
// Check for existing request ID header
|
|
166
|
+
const existingId = context.getHeader(header.toLowerCase());
|
|
167
|
+
const id = existingId ?? generator();
|
|
168
|
+
|
|
169
|
+
// Store in context
|
|
170
|
+
context.set("requestId", id);
|
|
171
|
+
|
|
172
|
+
const response = await next();
|
|
173
|
+
|
|
174
|
+
// Add to response header
|
|
175
|
+
response.headers.set(header, id);
|
|
176
|
+
|
|
177
|
+
return response;
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ============= Timing Middleware =============
|
|
182
|
+
|
|
183
|
+
export function timing(): Middleware {
|
|
184
|
+
return async (context: Context, next: () => Promise<Response>) => {
|
|
185
|
+
const start = performance.now();
|
|
186
|
+
|
|
187
|
+
const response = await next();
|
|
188
|
+
|
|
189
|
+
const duration = performance.now() - start;
|
|
190
|
+
response.headers.set("X-Response-Time", `${duration.toFixed(3)}ms`);
|
|
191
|
+
response.headers.set("Server-Timing", `total;dur=${duration.toFixed(3)}`);
|
|
192
|
+
|
|
193
|
+
return response;
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ============= Security Headers Middleware =============
|
|
198
|
+
|
|
199
|
+
export interface SecurityHeadersOptions {
|
|
200
|
+
contentSecurityPolicy?: string;
|
|
201
|
+
xssProtection?: boolean;
|
|
202
|
+
frameGuard?: "DENY" | "SAMEORIGIN" | "ALLOW-FROM";
|
|
203
|
+
hsts?: boolean | { maxAge?: number; includeSubDomains?: boolean };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function securityHeaders(
|
|
207
|
+
options: SecurityHeadersOptions = {},
|
|
208
|
+
): Middleware {
|
|
209
|
+
const {
|
|
210
|
+
xssProtection = true,
|
|
211
|
+
frameGuard = "SAMEORIGIN",
|
|
212
|
+
hsts = false,
|
|
213
|
+
} = options;
|
|
214
|
+
|
|
215
|
+
return async (context: Context, next: () => Promise<Response>) => {
|
|
216
|
+
const response = await next();
|
|
217
|
+
|
|
218
|
+
// XSS Protection
|
|
219
|
+
if (xssProtection) {
|
|
220
|
+
response.headers.set("X-XSS-Protection", "1; mode=block");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Frame Guard
|
|
224
|
+
if (frameGuard) {
|
|
225
|
+
response.headers.set("X-Frame-Options", frameGuard);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// HSTS
|
|
229
|
+
if (hsts) {
|
|
230
|
+
const maxAge =
|
|
231
|
+
typeof hsts === "object" ? (hsts.maxAge ?? 31536000) : 31536000;
|
|
232
|
+
const includeSubDomains =
|
|
233
|
+
typeof hsts === "object" ? (hsts.includeSubDomains ?? true) : true;
|
|
234
|
+
|
|
235
|
+
let hstsValue = `max-age=${maxAge}`;
|
|
236
|
+
if (includeSubDomains) {
|
|
237
|
+
hstsValue += "; includeSubDomains";
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
response.headers.set("Strict-Transport-Security", hstsValue);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Prevent MIME type sniffing
|
|
244
|
+
response.headers.set("X-Content-Type-Options", "nosniff");
|
|
245
|
+
|
|
246
|
+
return response;
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ============= Rate Limiter Middleware =============
|
|
251
|
+
|
|
252
|
+
export interface RateLimitOptions {
|
|
253
|
+
windowMs?: number;
|
|
254
|
+
max?: number;
|
|
255
|
+
keyGenerator?: (context: Context) => string;
|
|
256
|
+
handler?: (context: Context) => Response;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function rateLimit(options: RateLimitOptions = {}): Middleware {
|
|
260
|
+
const {
|
|
261
|
+
windowMs = 60000, // 1 minute
|
|
262
|
+
max = 100,
|
|
263
|
+
keyGenerator = (ctx) => ctx.ip ?? "unknown",
|
|
264
|
+
handler = (ctx) =>
|
|
265
|
+
ctx.error("Too many requests, please try again later.", 429),
|
|
266
|
+
} = options;
|
|
267
|
+
|
|
268
|
+
const hits = new Map<string, { count: number; resetTime: number }>();
|
|
269
|
+
|
|
270
|
+
// Cleanup old entries periodically
|
|
271
|
+
setInterval(() => {
|
|
272
|
+
const now = Date.now();
|
|
273
|
+
for (const [key, value] of hits.entries()) {
|
|
274
|
+
if (value.resetTime < now) {
|
|
275
|
+
hits.delete(key);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}, windowMs);
|
|
279
|
+
|
|
280
|
+
return async (context: Context, next: () => Promise<Response>) => {
|
|
281
|
+
const key = keyGenerator(context);
|
|
282
|
+
const now = Date.now();
|
|
283
|
+
|
|
284
|
+
let record = hits.get(key);
|
|
285
|
+
|
|
286
|
+
if (!record || record.resetTime < now) {
|
|
287
|
+
record = { count: 0, resetTime: now + windowMs };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
record.count++;
|
|
291
|
+
hits.set(key, record);
|
|
292
|
+
|
|
293
|
+
// Set rate limit headers
|
|
294
|
+
context.setHeader("X-RateLimit-Limit", String(max));
|
|
295
|
+
context.setHeader(
|
|
296
|
+
"X-RateLimit-Remaining",
|
|
297
|
+
String(Math.max(0, max - record.count)),
|
|
298
|
+
);
|
|
299
|
+
context.setHeader("X-RateLimit-Reset", String(record.resetTime));
|
|
300
|
+
|
|
301
|
+
if (record.count > max) {
|
|
302
|
+
return handler(context);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return next();
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ============= Compression Middleware =============
|
|
310
|
+
|
|
311
|
+
export interface CompressionOptions {
|
|
312
|
+
threshold?: number;
|
|
313
|
+
types?: string[];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function compression(options: CompressionOptions = {}): Middleware {
|
|
317
|
+
const { threshold = 1024 } = options;
|
|
318
|
+
|
|
319
|
+
return async (context: Context, next: () => Promise<Response>) => {
|
|
320
|
+
const response = await next();
|
|
321
|
+
|
|
322
|
+
// Check if client accepts gzip
|
|
323
|
+
const acceptEncoding = context.getHeader("accept-encoding") ?? "";
|
|
324
|
+
if (!acceptEncoding.includes("gzip")) {
|
|
325
|
+
return response;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Check content type
|
|
329
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
330
|
+
if (
|
|
331
|
+
!contentType.includes("text") &&
|
|
332
|
+
!contentType.includes("json") &&
|
|
333
|
+
!contentType.includes("javascript")
|
|
334
|
+
) {
|
|
335
|
+
return response;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Check size
|
|
339
|
+
const contentLength = Number.parseInt(
|
|
340
|
+
response.headers.get("content-length") ?? "0",
|
|
341
|
+
10,
|
|
342
|
+
);
|
|
343
|
+
if (contentLength > 0 && contentLength < threshold) {
|
|
344
|
+
return response;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Compress using Bun's built-in gzip
|
|
348
|
+
const buffer = await response.arrayBuffer();
|
|
349
|
+
const compressed = Bun.gzipSync(buffer);
|
|
350
|
+
|
|
351
|
+
return new Response(compressed, {
|
|
352
|
+
status: response.status,
|
|
353
|
+
headers: {
|
|
354
|
+
...Object.fromEntries(response.headers.entries()),
|
|
355
|
+
"Content-Encoding": "gzip",
|
|
356
|
+
"Content-Length": String(compressed.byteLength),
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
};
|
|
360
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware System
|
|
3
|
+
*
|
|
4
|
+
* Provides a composable middleware pipeline with async/await support
|
|
5
|
+
* and context mutation capabilities.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Context } from "../context";
|
|
9
|
+
|
|
10
|
+
// ============= Types =============
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Middleware function type
|
|
14
|
+
*/
|
|
15
|
+
export type Middleware = (
|
|
16
|
+
context: Context,
|
|
17
|
+
next: () => Promise<Response>,
|
|
18
|
+
) => Promise<Response> | Response;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Route handler type
|
|
22
|
+
*/
|
|
23
|
+
export type Handler = (context: Context) => Promise<Response> | Response;
|
|
24
|
+
|
|
25
|
+
// ============= Compose =============
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Compose multiple middleware into a single function
|
|
29
|
+
* Following Koa-style middleware pattern
|
|
30
|
+
*/
|
|
31
|
+
export function compose(
|
|
32
|
+
middleware: Middleware[],
|
|
33
|
+
): (context: Context, handler: Handler) => Promise<Response> {
|
|
34
|
+
return async (context: Context, handler: Handler): Promise<Response> => {
|
|
35
|
+
let index = -1;
|
|
36
|
+
|
|
37
|
+
const dispatch = async (i: number): Promise<Response> => {
|
|
38
|
+
if (i <= index) {
|
|
39
|
+
throw new Error("next() called multiple times");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
index = i;
|
|
43
|
+
|
|
44
|
+
// If we've run all middleware, call the handler
|
|
45
|
+
if (i >= middleware.length) {
|
|
46
|
+
return handler(context);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const fn = middleware[i];
|
|
50
|
+
|
|
51
|
+
return fn(context, async () => {
|
|
52
|
+
return dispatch(i + 1);
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return dispatch(0);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============= Pipeline Class =============
|
|
61
|
+
|
|
62
|
+
export class Pipeline {
|
|
63
|
+
private middleware: Middleware[] = [];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Add middleware to the pipeline
|
|
67
|
+
*/
|
|
68
|
+
use(middleware: Middleware): this {
|
|
69
|
+
this.middleware.push(middleware);
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Execute the pipeline with a handler
|
|
75
|
+
*/
|
|
76
|
+
async execute(context: Context, handler: Handler): Promise<Response> {
|
|
77
|
+
const fn = compose(this.middleware);
|
|
78
|
+
return fn(context, handler);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get middleware count
|
|
83
|
+
*/
|
|
84
|
+
get length(): number {
|
|
85
|
+
return this.middleware.length;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create a new middleware pipeline
|
|
91
|
+
*/
|
|
92
|
+
export function createPipeline(): Pipeline {
|
|
93
|
+
return new Pipeline();
|
|
94
|
+
}
|