@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.
Files changed (120) hide show
  1. package/.env.example +109 -0
  2. package/.github/workflows/ci.yml +31 -0
  3. package/LICENSE +21 -0
  4. package/README.md +892 -0
  5. package/architecture.md +652 -0
  6. package/bun.lock +70 -0
  7. package/dist/cli/index.js +3233 -0
  8. package/dist/index.js +9014 -0
  9. package/package.json +77 -0
  10. package/src/cache/index.ts +795 -0
  11. package/src/cli/ARCHITECTURE.md +837 -0
  12. package/src/cli/bin.ts +10 -0
  13. package/src/cli/commands/build.ts +425 -0
  14. package/src/cli/commands/dev.ts +248 -0
  15. package/src/cli/commands/generate.ts +541 -0
  16. package/src/cli/commands/help.ts +55 -0
  17. package/src/cli/commands/index.ts +112 -0
  18. package/src/cli/commands/migration.ts +355 -0
  19. package/src/cli/commands/new.ts +804 -0
  20. package/src/cli/commands/start.ts +208 -0
  21. package/src/cli/core/args.ts +283 -0
  22. package/src/cli/core/console.ts +349 -0
  23. package/src/cli/core/index.ts +60 -0
  24. package/src/cli/core/prompt.ts +424 -0
  25. package/src/cli/core/spinner.ts +265 -0
  26. package/src/cli/index.ts +135 -0
  27. package/src/cli/templates/deploy.ts +295 -0
  28. package/src/cli/templates/docker.ts +307 -0
  29. package/src/cli/templates/index.ts +24 -0
  30. package/src/cli/utils/fs.ts +428 -0
  31. package/src/cli/utils/index.ts +8 -0
  32. package/src/cli/utils/strings.ts +197 -0
  33. package/src/config/env.ts +408 -0
  34. package/src/config/index.ts +506 -0
  35. package/src/config/loader.ts +329 -0
  36. package/src/config/merge.ts +285 -0
  37. package/src/config/types.ts +320 -0
  38. package/src/config/validation.ts +441 -0
  39. package/src/container/forward-ref.ts +143 -0
  40. package/src/container/index.ts +386 -0
  41. package/src/context/index.ts +360 -0
  42. package/src/database/index.ts +1142 -0
  43. package/src/database/migrations/index.ts +371 -0
  44. package/src/database/schema/index.ts +619 -0
  45. package/src/frontend/api-routes.ts +640 -0
  46. package/src/frontend/bundler.ts +643 -0
  47. package/src/frontend/console-client.ts +419 -0
  48. package/src/frontend/console-stream.ts +587 -0
  49. package/src/frontend/dev-server.ts +846 -0
  50. package/src/frontend/file-router.ts +611 -0
  51. package/src/frontend/frameworks/index.ts +106 -0
  52. package/src/frontend/frameworks/react.ts +85 -0
  53. package/src/frontend/frameworks/solid.ts +104 -0
  54. package/src/frontend/frameworks/svelte.ts +110 -0
  55. package/src/frontend/frameworks/vue.ts +92 -0
  56. package/src/frontend/hmr-client.ts +663 -0
  57. package/src/frontend/hmr.ts +728 -0
  58. package/src/frontend/index.ts +342 -0
  59. package/src/frontend/islands.ts +552 -0
  60. package/src/frontend/isr.ts +555 -0
  61. package/src/frontend/layout.ts +475 -0
  62. package/src/frontend/ssr/react.ts +446 -0
  63. package/src/frontend/ssr/solid.ts +523 -0
  64. package/src/frontend/ssr/svelte.ts +546 -0
  65. package/src/frontend/ssr/vue.ts +504 -0
  66. package/src/frontend/ssr.ts +699 -0
  67. package/src/frontend/types.ts +2274 -0
  68. package/src/health/index.ts +604 -0
  69. package/src/index.ts +410 -0
  70. package/src/lock/index.ts +587 -0
  71. package/src/logger/index.ts +444 -0
  72. package/src/logger/transports/index.ts +969 -0
  73. package/src/metrics/index.ts +494 -0
  74. package/src/middleware/built-in.ts +360 -0
  75. package/src/middleware/index.ts +94 -0
  76. package/src/modules/filters.ts +458 -0
  77. package/src/modules/guards.ts +405 -0
  78. package/src/modules/index.ts +1256 -0
  79. package/src/modules/interceptors.ts +574 -0
  80. package/src/modules/lazy.ts +418 -0
  81. package/src/modules/lifecycle.ts +478 -0
  82. package/src/modules/metadata.ts +90 -0
  83. package/src/modules/pipes.ts +626 -0
  84. package/src/router/index.ts +339 -0
  85. package/src/router/linear.ts +371 -0
  86. package/src/router/regex.ts +292 -0
  87. package/src/router/tree.ts +562 -0
  88. package/src/rpc/index.ts +1263 -0
  89. package/src/security/index.ts +436 -0
  90. package/src/ssg/index.ts +631 -0
  91. package/src/storage/index.ts +456 -0
  92. package/src/telemetry/index.ts +1097 -0
  93. package/src/testing/index.ts +1586 -0
  94. package/src/types/index.ts +236 -0
  95. package/src/types/optional-deps.d.ts +219 -0
  96. package/src/validation/index.ts +276 -0
  97. package/src/websocket/index.ts +1004 -0
  98. package/tests/integration/cli.test.ts +1016 -0
  99. package/tests/integration/fullstack.test.ts +234 -0
  100. package/tests/unit/cache.test.ts +174 -0
  101. package/tests/unit/cli-commands.test.ts +892 -0
  102. package/tests/unit/cli.test.ts +1258 -0
  103. package/tests/unit/container.test.ts +279 -0
  104. package/tests/unit/context.test.ts +221 -0
  105. package/tests/unit/database.test.ts +183 -0
  106. package/tests/unit/linear-router.test.ts +280 -0
  107. package/tests/unit/lock.test.ts +336 -0
  108. package/tests/unit/middleware.test.ts +184 -0
  109. package/tests/unit/modules.test.ts +142 -0
  110. package/tests/unit/pubsub.test.ts +257 -0
  111. package/tests/unit/regex-router.test.ts +265 -0
  112. package/tests/unit/router.test.ts +373 -0
  113. package/tests/unit/rpc.test.ts +1248 -0
  114. package/tests/unit/security.test.ts +174 -0
  115. package/tests/unit/telemetry.test.ts +371 -0
  116. package/tests/unit/test-cache.test.ts +110 -0
  117. package/tests/unit/test-database.test.ts +282 -0
  118. package/tests/unit/tree-router.test.ts +325 -0
  119. package/tests/unit/validation.test.ts +794 -0
  120. package/tsconfig.json +27 -0
@@ -0,0 +1,640 @@
1
+ /**
2
+ * API Routes Implementation
3
+ *
4
+ * Provides Next.js-style API routes:
5
+ * - pages/api/ directory for API endpoints
6
+ * - HTTP method handlers (GET, POST, PUT, PATCH, DELETE, etc.)
7
+ * - Request context with params, query, body
8
+ * - Middleware support via _middleware.ts
9
+ * - Type-safe response helpers
10
+ */
11
+
12
+ import { createLogger, type Logger } from "../logger/index.js";
13
+ import type {
14
+ APIRouteConfig,
15
+ PartialAPIRouteConfig,
16
+ APIRouteDefinition,
17
+ APIRouteHandler,
18
+ APIContext,
19
+ APIResponse,
20
+ APIMiddleware,
21
+ APIRouteModule,
22
+ HTTPMethod,
23
+ } from "./types.js";
24
+
25
+ // ============= Constants =============
26
+
27
+ const DEFAULT_API_DIR = "pages/api";
28
+ const SUPPORTED_EXTENSIONS = [".ts", ".js"];
29
+ const SUPPORTED_METHODS: HTTPMethod[] = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
30
+
31
+ // ============= API Route Manager Class =============
32
+
33
+ /**
34
+ * API Route Manager handles API endpoint registration and routing
35
+ *
36
+ * Features:
37
+ * - pages/api/ directory scanning
38
+ * - HTTP method handlers
39
+ * - Request context with params, query, body
40
+ * - Middleware support
41
+ * - Type-safe response helpers
42
+ */
43
+ export class APIRouteManager {
44
+ private config: APIRouteConfig;
45
+ private logger: Logger;
46
+ private routes: Map<string, APIRouteDefinition> = new Map();
47
+ private middlewares: Map<string, APIMiddleware[]> = new Map();
48
+ private modules: Map<string, APIRouteModule> = new Map();
49
+
50
+ constructor(config: PartialAPIRouteConfig = {}) {
51
+ this.config = this.normalizeConfig(config);
52
+ this.logger = createLogger({
53
+ level: "debug",
54
+ pretty: true,
55
+ context: { component: "APIRouteManager" },
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Normalize partial config to full config with defaults
61
+ */
62
+ private normalizeConfig(config: PartialAPIRouteConfig): APIRouteConfig {
63
+ return {
64
+ apiDir: config.apiDir ?? DEFAULT_API_DIR,
65
+ rootDir: config.rootDir ?? process.cwd(),
66
+ extensions: config.extensions ?? SUPPORTED_EXTENSIONS,
67
+ bodyLimit: config.bodyLimit ?? 1024 * 1024, // 1MB default
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Initialize the API route manager by scanning for API files
73
+ */
74
+ async init(): Promise<void> {
75
+ this.logger.info(`Initializing API routes from: ${this.config.apiDir}`);
76
+ await this.scanAPIDirectory();
77
+ this.logger.info(`Loaded ${this.routes.size} API routes`);
78
+ }
79
+
80
+ /**
81
+ * Scan the API directory for route files
82
+ */
83
+ private async scanAPIDirectory(): Promise<void> {
84
+ const apiPath = this.config.apiDir;
85
+ const glob = new Bun.Glob(`**/*{${this.config.extensions.join(",")}}`);
86
+
87
+ try {
88
+ for await (const file of glob.scan(apiPath)) {
89
+ // Skip middleware files
90
+ if (file.includes("_middleware")) continue;
91
+
92
+ await this.processAPIFile(file, apiPath);
93
+ }
94
+
95
+ // Load middlewares after routes
96
+ await this.loadMiddlewares(apiPath);
97
+ } catch (error) {
98
+ this.logger.error(`Failed to scan API directory: ${apiPath}`, error);
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Process a single API file
104
+ */
105
+ private async processAPIFile(filePath: string, basePath: string): Promise<void> {
106
+ const fullPath = `${basePath}/${filePath}`;
107
+ const routePath = this.filePathToRoute(filePath);
108
+
109
+ // Load the module to check for handlers
110
+ const module = await this.loadModule(fullPath);
111
+ if (!module) return;
112
+
113
+ // Check which HTTP methods are exported
114
+ const methods: HTTPMethod[] = [];
115
+ for (const method of SUPPORTED_METHODS) {
116
+ if (typeof module[method] === "function") {
117
+ methods.push(method);
118
+ }
119
+ }
120
+
121
+ if (methods.length === 0) {
122
+ this.logger.warn(`No HTTP handlers found in: ${filePath}`);
123
+ return;
124
+ }
125
+
126
+ // Parse route parameters
127
+ const params = this.parseRouteParams(routePath);
128
+
129
+ const route: APIRouteDefinition = {
130
+ id: this.generateRouteId(filePath),
131
+ path: routePath,
132
+ filePath: fullPath,
133
+ methods,
134
+ params,
135
+ regex: this.routeToRegex(routePath),
136
+ };
137
+
138
+ this.routes.set(routePath, route);
139
+ this.logger.debug(`Processed API route: ${routePath} [${methods.join(", ")}]`);
140
+ }
141
+
142
+ /**
143
+ * Load middlewares from _middleware.ts files
144
+ */
145
+ private async loadMiddlewares(basePath: string): Promise<void> {
146
+ const glob = new Bun.Glob(`**/_middleware{${this.config.extensions.join(",")}}`);
147
+
148
+ try {
149
+ for await (const file of glob.scan(basePath)) {
150
+ const fullPath = `${basePath}/${file}`;
151
+ const segment = this.getMiddlewareSegment(file);
152
+
153
+ const module = await import(fullPath);
154
+ const middleware = module.default || module.middleware;
155
+
156
+ if (middleware) {
157
+ if (Array.isArray(middleware)) {
158
+ this.middlewares.set(segment, middleware);
159
+ } else {
160
+ this.middlewares.set(segment, [middleware]);
161
+ }
162
+ this.logger.debug(`Loaded middleware for: ${segment}`);
163
+ }
164
+ }
165
+ } catch (error) {
166
+ this.logger.error("Failed to load middlewares", error);
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Get middleware segment from file path
172
+ */
173
+ private getMiddlewareSegment(filePath: string): string {
174
+ const segment = filePath.replace(/\/_middleware\.(ts|js)$/, "");
175
+ return segment === "" ? "/" : `/${segment}`;
176
+ }
177
+
178
+ /**
179
+ * Convert file path to API route path
180
+ */
181
+ private filePathToRoute(filePath: string): string {
182
+ // Remove file extension
183
+ let route = filePath.replace(/\.(ts|js)$/, "");
184
+
185
+ // Convert index to root
186
+ if (route === "index") {
187
+ return "/api";
188
+ }
189
+
190
+ // Handle nested index files
191
+ if (route.endsWith("/index")) {
192
+ route = route.replace(/\/index$/, "");
193
+ }
194
+
195
+ // Ensure leading /api
196
+ if (!route.startsWith("api/")) {
197
+ route = `api/${route}`;
198
+ }
199
+
200
+ return `/${route}`;
201
+ }
202
+
203
+ /**
204
+ * Parse route parameters from path
205
+ */
206
+ private parseRouteParams(path: string): string[] {
207
+ const params: string[] = [];
208
+ const regex = /\[([^\]]+)\]/g;
209
+ let match;
210
+
211
+ while ((match = regex.exec(path)) !== null) {
212
+ params.push(match[1]);
213
+ }
214
+
215
+ return params;
216
+ }
217
+
218
+ /**
219
+ * Convert route path to regex
220
+ */
221
+ private routeToRegex(routePath: string): RegExp {
222
+ let regex = routePath;
223
+
224
+ // Escape special regex characters
225
+ regex = regex.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
226
+
227
+ // Replace catch-all params [...param] with capture group
228
+ regex = regex.replace(/\\\[\\\.\.\.([^\]]+)\\\]/g, "(.*)");
229
+
230
+ // Replace single params [param] with capture group
231
+ regex = regex.replace(/\\\[([^\]]+)\\\]/g, "([^/]+)");
232
+
233
+ return new RegExp(`^${regex}$`);
234
+ }
235
+
236
+ /**
237
+ * Generate unique route ID
238
+ */
239
+ private generateRouteId(filePath: string): string {
240
+ return `api-${filePath.replace(/[\/\\.]/g, "-")}`;
241
+ }
242
+
243
+ /**
244
+ * Load API module
245
+ */
246
+ private async loadModule(filePath: string): Promise<APIRouteModule | null> {
247
+ if (this.modules.has(filePath)) {
248
+ return this.modules.get(filePath)!;
249
+ }
250
+
251
+ try {
252
+ const module = await import(filePath);
253
+ this.modules.set(filePath, module);
254
+ return module;
255
+ } catch (error) {
256
+ this.logger.error(`Failed to load API module: ${filePath}`, error);
257
+ return null;
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Match a request to an API route
263
+ */
264
+ match(method: string, pathname: string): { route: APIRouteDefinition; params: Record<string, string> } | null {
265
+ for (const route of this.routes.values()) {
266
+ const match = pathname.match(route.regex);
267
+ if (match) {
268
+ // Check if method is supported
269
+ if (!route.methods.includes(method as HTTPMethod)) {
270
+ continue;
271
+ }
272
+
273
+ // Extract params
274
+ const params: Record<string, string> = {};
275
+ for (let i = 0; i < route.params.length; i++) {
276
+ params[route.params[i]] = match[i + 1];
277
+ }
278
+
279
+ return { route, params };
280
+ }
281
+ }
282
+
283
+ return null;
284
+ }
285
+
286
+ /**
287
+ * Handle incoming API request
288
+ */
289
+ async handle(request: Request): Promise<Response> {
290
+ const url = new URL(request.url);
291
+ const pathname = url.pathname;
292
+ const method = request.method as HTTPMethod;
293
+
294
+ const match = this.match(method, pathname);
295
+
296
+ if (!match) {
297
+ // Check if route exists but method not allowed
298
+ const routeExists = this.routeExists(pathname);
299
+ if (routeExists) {
300
+ return this.jsonResponse({ error: "Method Not Allowed" }, 405);
301
+ }
302
+ return this.jsonResponse({ error: "Not Found" }, 404);
303
+ }
304
+
305
+ const { route, params } = match;
306
+
307
+ // Create context
308
+ const context = await this.createContext(request, params);
309
+
310
+ // Get middlewares
311
+ const middlewares = this.getMiddlewaresForPath(pathname);
312
+
313
+ // Run middlewares and handler
314
+ try {
315
+ return await this.runWithMiddleware(context, middlewares, route, method);
316
+ } catch (error) {
317
+ this.logger.error(`API error: ${pathname}`, error);
318
+ return this.jsonResponse(
319
+ { error: "Internal Server Error", message: error instanceof Error ? error.message : "Unknown error" },
320
+ 500
321
+ );
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Check if a route exists (any method)
327
+ */
328
+ private routeExists(pathname: string): boolean {
329
+ for (const route of this.routes.values()) {
330
+ if (pathname.match(route.regex)) {
331
+ return true;
332
+ }
333
+ }
334
+ return false;
335
+ }
336
+
337
+ /**
338
+ * Create API context from request
339
+ */
340
+ private async createContext(
341
+ request: Request,
342
+ params: Record<string, string>
343
+ ): Promise<APIContext> {
344
+ const url = new URL(request.url);
345
+
346
+ // Parse body based on content type
347
+ let body: unknown = null;
348
+ const contentType = request.headers.get("content-type") || "";
349
+
350
+ if (request.body) {
351
+ try {
352
+ if (contentType.includes("application/json")) {
353
+ body = await request.json();
354
+ } else if (contentType.includes("application/x-www-form-urlencoded")) {
355
+ const formData = await request.formData();
356
+ body = Object.fromEntries(formData);
357
+ } else if (contentType.includes("multipart/form-data")) {
358
+ const formData = await request.formData();
359
+ body = formData;
360
+ } else {
361
+ body = await request.text();
362
+ }
363
+ } catch (error) {
364
+ this.logger.warn("Failed to parse request body", error as Record<string, unknown>);
365
+ }
366
+ }
367
+
368
+ return {
369
+ request,
370
+ url: request.url,
371
+ pathname: url.pathname,
372
+ query: url.searchParams,
373
+ params,
374
+ body,
375
+ headers: request.headers,
376
+ method: request.method as HTTPMethod,
377
+ cookies: this.parseCookies(request.headers.get("cookie") || ""),
378
+ };
379
+ }
380
+
381
+ /**
382
+ * Parse cookies from header
383
+ */
384
+ private parseCookies(cookieHeader: string): Record<string, string> {
385
+ const cookies: Record<string, string> = {};
386
+
387
+ if (!cookieHeader) return cookies;
388
+
389
+ for (const cookie of cookieHeader.split(";")) {
390
+ const [name, value] = cookie.trim().split("=");
391
+ if (name && value) {
392
+ cookies[name] = decodeURIComponent(value);
393
+ }
394
+ }
395
+
396
+ return cookies;
397
+ }
398
+
399
+ /**
400
+ * Get middlewares for a path
401
+ */
402
+ private getMiddlewaresForPath(pathname: string): APIMiddleware[] {
403
+ const middlewares: APIMiddleware[] = [];
404
+
405
+ // Check each middleware segment
406
+ for (const [segment, segmentMiddlewares] of this.middlewares) {
407
+ if (segment === "/" || pathname.startsWith(segment)) {
408
+ middlewares.push(...segmentMiddlewares);
409
+ }
410
+ }
411
+
412
+ return middlewares;
413
+ }
414
+
415
+ /**
416
+ * Run request through middlewares and handler
417
+ */
418
+ private async runWithMiddleware(
419
+ context: APIContext,
420
+ middlewares: APIMiddleware[],
421
+ route: APIRouteDefinition,
422
+ method: HTTPMethod
423
+ ): Promise<Response> {
424
+ let index = 0;
425
+
426
+ const next = async (): Promise<Response> => {
427
+ if (index < middlewares.length) {
428
+ const middleware = middlewares[index++];
429
+ return middleware(context, next);
430
+ }
431
+
432
+ // Run the actual handler
433
+ const module = await this.loadModule(route.filePath);
434
+ if (!module || !module[method]) {
435
+ return this.jsonResponse({ error: "Handler Not Found" }, 404);
436
+ }
437
+
438
+ const result = await module[method](context);
439
+ return this.normalizeResponse(result);
440
+ };
441
+
442
+ return next();
443
+ }
444
+
445
+ /**
446
+ * Normalize response to Response object
447
+ */
448
+ private normalizeResponse(result: APIResponse | Response): Response {
449
+ if (result instanceof Response) {
450
+ return result;
451
+ }
452
+
453
+ if (typeof result === "string") {
454
+ return new Response(result, {
455
+ headers: { "Content-Type": "text/plain" },
456
+ });
457
+ }
458
+
459
+ return this.jsonResponse(result);
460
+ }
461
+
462
+ /**
463
+ * Create JSON response
464
+ */
465
+ private jsonResponse(data: unknown, status = 200): Response {
466
+ return new Response(JSON.stringify(data), {
467
+ status,
468
+ headers: {
469
+ "Content-Type": "application/json",
470
+ },
471
+ });
472
+ }
473
+
474
+ /**
475
+ * Get all routes
476
+ */
477
+ getRoutes(): APIRouteDefinition[] {
478
+ return Array.from(this.routes.values());
479
+ }
480
+
481
+ /**
482
+ * Get route by path
483
+ */
484
+ getRoute(path: string): APIRouteDefinition | undefined {
485
+ return this.routes.get(path);
486
+ }
487
+
488
+ /**
489
+ * Reload routes (for hot reload)
490
+ */
491
+ async reload(): Promise<void> {
492
+ this.logger.info("Reloading API routes...");
493
+ this.routes.clear();
494
+ this.middlewares.clear();
495
+ this.modules.clear();
496
+ await this.init();
497
+ }
498
+
499
+ /**
500
+ * Get configuration
501
+ */
502
+ getConfig(): APIRouteConfig {
503
+ return { ...this.config };
504
+ }
505
+ }
506
+
507
+ // ============= Factory Function =============
508
+
509
+ /**
510
+ * Create an API route manager
511
+ */
512
+ export function createAPIRouteManager(config: PartialAPIRouteConfig = {}): APIRouteManager {
513
+ return new APIRouteManager(config);
514
+ }
515
+
516
+ // ============= Response Helpers =============
517
+
518
+ /**
519
+ * Create a JSON response
520
+ */
521
+ export function json(data: unknown, status = 200, headers?: Record<string, string>): Response {
522
+ return new Response(JSON.stringify(data), {
523
+ status,
524
+ headers: {
525
+ "Content-Type": "application/json",
526
+ ...headers,
527
+ },
528
+ });
529
+ }
530
+
531
+ /**
532
+ * Create a text response
533
+ */
534
+ export function text(data: string, status = 200, headers?: Record<string, string>): Response {
535
+ return new Response(data, {
536
+ status,
537
+ headers: {
538
+ "Content-Type": "text/plain",
539
+ ...headers,
540
+ },
541
+ });
542
+ }
543
+
544
+ /**
545
+ * Create an HTML response
546
+ */
547
+ export function html(data: string, status = 200, headers?: Record<string, string>): Response {
548
+ return new Response(data, {
549
+ status,
550
+ headers: {
551
+ "Content-Type": "text/html",
552
+ ...headers,
553
+ },
554
+ });
555
+ }
556
+
557
+ /**
558
+ * Create a redirect response
559
+ */
560
+ export function redirect(url: string, status = 302): Response {
561
+ return new Response(null, {
562
+ status,
563
+ headers: {
564
+ Location: url,
565
+ },
566
+ });
567
+ }
568
+
569
+ /**
570
+ * Create an error response
571
+ */
572
+ export function error(message: string, status = 500): Response {
573
+ return json({ error: message }, status);
574
+ }
575
+
576
+ /**
577
+ * Create a 404 Not Found response
578
+ */
579
+ export function notFound(message = "Not Found"): Response {
580
+ return json({ error: message }, 404);
581
+ }
582
+
583
+ /**
584
+ * Create a 401 Unauthorized response
585
+ */
586
+ export function unauthorized(message = "Unauthorized"): Response {
587
+ return json({ error: message }, 401);
588
+ }
589
+
590
+ /**
591
+ * Create a 403 Forbidden response
592
+ */
593
+ export function forbidden(message = "Forbidden"): Response {
594
+ return json({ error: message }, 403);
595
+ }
596
+
597
+ /**
598
+ * Create a 400 Bad Request response
599
+ */
600
+ export function badRequest(message = "Bad Request"): Response {
601
+ return json({ error: message }, 400);
602
+ }
603
+
604
+ /**
605
+ * Create a 201 Created response
606
+ */
607
+ export function created(data: unknown): Response {
608
+ return json(data, 201);
609
+ }
610
+
611
+ /**
612
+ * Create a 204 No Content response
613
+ */
614
+ export function noContent(): Response {
615
+ return new Response(null, { status: 204 });
616
+ }
617
+
618
+ // ============= Utility Functions =============
619
+
620
+ /**
621
+ * Check if a file is an API route file
622
+ */
623
+ export function isAPIRouteFile(filename: string): boolean {
624
+ return SUPPORTED_EXTENSIONS.some(ext => filename.endsWith(ext)) &&
625
+ !filename.includes("_middleware");
626
+ }
627
+
628
+ /**
629
+ * Check if a file is a middleware file
630
+ */
631
+ export function isMiddlewareFile(filename: string): boolean {
632
+ return filename.includes("_middleware");
633
+ }
634
+
635
+ /**
636
+ * Get HTTP methods from module
637
+ */
638
+ export function getModuleMethods(module: APIRouteModule): HTTPMethod[] {
639
+ return SUPPORTED_METHODS.filter(method => typeof module[method] === "function");
640
+ }