@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,562 @@
1
+ /**
2
+ * TreeRouter (Radix Tree Router)
3
+ *
4
+ * Optimized for applications with 100+ routes.
5
+ * - O(log n) lookups via radix tree traversal
6
+ * - Efficient prefix compression
7
+ * - Supports parameters, wildcards, and regex constraints
8
+ *
9
+ * Best for: Large APIs, enterprise applications, complex routing
10
+ *
11
+ * Tree Structure Example:
12
+ *
13
+ * root
14
+ * |
15
+ * /api
16
+ * / \
17
+ * /users /posts
18
+ * / \ \
19
+ * /:id /list /:id
20
+ * | | |
21
+ * GET GET GET
22
+ */
23
+
24
+ import type {
25
+ HTTPMethod,
26
+ MiddlewareHandler,
27
+ PathParams,
28
+ RouteHandler,
29
+ } from "../types";
30
+
31
+ // ============= Types =============
32
+
33
+ export interface RouteMatch {
34
+ handler: RouteHandler;
35
+ params: PathParams;
36
+ middleware?: MiddlewareHandler[];
37
+ name?: string;
38
+ }
39
+
40
+ export interface RouteOptions {
41
+ name?: string;
42
+ middleware?: MiddlewareHandler | MiddlewareHandler[];
43
+ }
44
+
45
+ interface RouteHandlerEntry {
46
+ handler: RouteHandler;
47
+ middleware?: MiddlewareHandler[];
48
+ name?: string;
49
+ }
50
+
51
+ interface TreeNode {
52
+ /** Path segment (e.g., "users", ":id", "") */
53
+ path: string;
54
+
55
+ /** Children indexed by path prefix */
56
+ children: Map<string, TreeNode>;
57
+
58
+ /** Parameter child (for :param patterns) */
59
+ paramChild?: TreeNode;
60
+
61
+ /** Parameter name if this is a param node */
62
+ paramName?: string;
63
+
64
+ /** Regex constraint for parameter */
65
+ paramRegex?: RegExp;
66
+
67
+ /** Wildcard child */
68
+ wildcardChild?: TreeNode;
69
+
70
+ /** Handlers indexed by HTTP method */
71
+ handlers: Map<HTTPMethod | "ALL", RouteHandlerEntry>;
72
+
73
+ /** Is this a wildcard node? */
74
+ isWildcard: boolean;
75
+ }
76
+
77
+ // ============= TreeRouter Class =============
78
+
79
+ export class TreeRouter {
80
+ private root: TreeNode;
81
+ private groupPrefix = "";
82
+ private groupMiddleware: MiddlewareHandler[] = [];
83
+ private routeCount = 0;
84
+
85
+ constructor() {
86
+ this.root = this.createNode("");
87
+ }
88
+
89
+ private createNode(
90
+ path: string,
91
+ paramName?: string,
92
+ paramRegex?: RegExp,
93
+ isWildcard = false,
94
+ ): TreeNode {
95
+ return {
96
+ path,
97
+ children: new Map(),
98
+ handlers: new Map(),
99
+ paramName,
100
+ paramRegex,
101
+ isWildcard,
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Register a route with specific HTTP method
107
+ */
108
+ private addRoute(
109
+ method: HTTPMethod | "ALL",
110
+ pattern: string,
111
+ handler: RouteHandler,
112
+ options?: RouteOptions,
113
+ ): void {
114
+ const fullPattern = this.groupPrefix + pattern;
115
+
116
+ const optsMiddleware = options?.middleware;
117
+ const routeMiddleware: MiddlewareHandler[] = optsMiddleware
118
+ ? Array.isArray(optsMiddleware)
119
+ ? optsMiddleware
120
+ : [optsMiddleware]
121
+ : [];
122
+
123
+ const middleware = [...this.groupMiddleware, ...routeMiddleware];
124
+
125
+ // Parse pattern into segments
126
+ const segments = this.parsePattern(fullPattern);
127
+
128
+ // Insert into tree
129
+ const node = this.insertSegments(this.root, segments, 0);
130
+
131
+ // Store handler
132
+ node.handlers.set(method, {
133
+ handler,
134
+ middleware,
135
+ name: options?.name,
136
+ });
137
+
138
+ this.routeCount++;
139
+ }
140
+
141
+ /**
142
+ * Parse a pattern into segments
143
+ *
144
+ * Examples:
145
+ * - "/users/:id" -> [{ type: 'static', value: 'users' }, { type: 'param', name: 'id' }]
146
+ * - "/files/*" -> [{ type: 'static', value: 'files' }, { type: 'wildcard' }]
147
+ */
148
+ private parsePattern(pattern: string): Array<{
149
+ type: "static" | "param" | "wildcard";
150
+ value?: string;
151
+ name?: string;
152
+ regex?: RegExp;
153
+ optional?: boolean;
154
+ }> {
155
+ const segments: Array<{
156
+ type: "static" | "param" | "wildcard";
157
+ value?: string;
158
+ name?: string;
159
+ regex?: RegExp;
160
+ optional?: boolean;
161
+ }> = [];
162
+
163
+ // Normalize pattern
164
+ let normalized = pattern;
165
+ if (!normalized.startsWith("/")) {
166
+ normalized = `/${normalized}`;
167
+ }
168
+
169
+ // Remove trailing slash (except for root)
170
+ if (normalized.length > 1 && normalized.endsWith("/")) {
171
+ normalized = normalized.slice(0, -1);
172
+ }
173
+
174
+ if (normalized === "/") {
175
+ return [{ type: "static", value: "" }];
176
+ }
177
+
178
+ const parts = normalized.split("/").filter(Boolean);
179
+
180
+ for (const part of parts) {
181
+ if (part === "*") {
182
+ segments.push({ type: "wildcard" });
183
+ } else if (part.startsWith(":")) {
184
+ let name = part.slice(1);
185
+ let regex: RegExp | undefined;
186
+ let optional = false;
187
+
188
+ // Check for optional marker
189
+ if (name.endsWith("?")) {
190
+ optional = true;
191
+ name = name.slice(0, -1);
192
+ }
193
+
194
+ // Check for custom regex
195
+ const regexMatch = name.match(/^(\w+)<(.+)>$/);
196
+ if (regexMatch) {
197
+ name = regexMatch[1];
198
+ regex = new RegExp(`^(${regexMatch[2]})$`);
199
+ }
200
+
201
+ segments.push({ type: "param", name, regex, optional });
202
+ } else {
203
+ segments.push({ type: "static", value: part.toLowerCase() });
204
+ }
205
+ }
206
+
207
+ return segments;
208
+ }
209
+
210
+ /**
211
+ * Insert segments into the tree, creating nodes as needed
212
+ */
213
+ private insertSegments(
214
+ node: TreeNode,
215
+ segments: Array<{
216
+ type: "static" | "param" | "wildcard";
217
+ value?: string;
218
+ name?: string;
219
+ regex?: RegExp;
220
+ optional?: boolean;
221
+ }>,
222
+ index: number,
223
+ ): TreeNode {
224
+ if (index >= segments.length) {
225
+ return node;
226
+ }
227
+
228
+ const segment = segments[index];
229
+
230
+ if (segment.type === "wildcard") {
231
+ // Create or get wildcard child
232
+ if (!node.wildcardChild) {
233
+ node.wildcardChild = this.createNode("*", "*", undefined, true);
234
+ }
235
+ return node.wildcardChild;
236
+ }
237
+
238
+ if (segment.type === "param") {
239
+ // Create or get parameter child
240
+ if (!node.paramChild) {
241
+ node.paramChild = this.createNode(
242
+ `:${segment.name}`,
243
+ segment.name,
244
+ segment.regex,
245
+ );
246
+ }
247
+
248
+ // Continue with next segment
249
+ return this.insertSegments(node.paramChild, segments, index + 1);
250
+ }
251
+
252
+ // Static segment
253
+ const value = segment.value!;
254
+
255
+ // Look for existing child with common prefix
256
+ let child = node.children.get(value);
257
+
258
+ if (!child) {
259
+ // Create new child
260
+ child = this.createNode(value);
261
+ node.children.set(value, child);
262
+ }
263
+
264
+ // Continue with next segment
265
+ return this.insertSegments(child, segments, index + 1);
266
+ }
267
+
268
+ /**
269
+ * Match a route by method and pathname
270
+ *
271
+ * Performance: O(log n) average case for tree traversal
272
+ */
273
+ match(method: HTTPMethod | "ALL", pathname: string): RouteMatch | undefined {
274
+ const params: PathParams = {};
275
+
276
+ // Normalize pathname
277
+ let normalized = pathname.toLowerCase();
278
+ if (normalized.length > 1 && normalized.endsWith("/")) {
279
+ normalized = normalized.slice(0, -1);
280
+ }
281
+
282
+ // Split into parts
283
+ const parts =
284
+ normalized === "/" ? [""] : normalized.split("/").filter(Boolean);
285
+
286
+ // Search tree
287
+ const result = this.searchTree(this.root, parts, 0, method, params);
288
+
289
+ return result;
290
+ }
291
+
292
+ /**
293
+ * Search the tree for a matching route
294
+ */
295
+ private searchTree(
296
+ node: TreeNode,
297
+ parts: string[],
298
+ partIndex: number,
299
+ method: HTTPMethod | "ALL",
300
+ params: PathParams,
301
+ ): RouteMatch | undefined {
302
+ // Check if we've consumed all parts
303
+ if (partIndex >= parts.length) {
304
+ // Check for handler
305
+ const handlerEntry =
306
+ node.handlers.get(method) || node.handlers.get("ALL");
307
+ if (handlerEntry) {
308
+ return {
309
+ handler: handlerEntry.handler,
310
+ params: { ...params },
311
+ middleware: handlerEntry.middleware,
312
+ name: handlerEntry.name,
313
+ };
314
+ }
315
+ return undefined;
316
+ }
317
+
318
+ const part = parts[partIndex];
319
+
320
+ // 1. Try exact match (static child) - highest priority
321
+ const staticChild = node.children.get(part);
322
+ if (staticChild) {
323
+ const result = this.searchTree(
324
+ staticChild,
325
+ parts,
326
+ partIndex + 1,
327
+ method,
328
+ params,
329
+ );
330
+ if (result) return result;
331
+ }
332
+
333
+ // 2. Try parameter child
334
+ if (node.paramChild) {
335
+ const paramNode = node.paramChild;
336
+
337
+ // Check regex constraint
338
+ if (paramNode.paramRegex) {
339
+ if (paramNode.paramRegex.test(part)) {
340
+ params[paramNode.paramName!] = part;
341
+ const result = this.searchTree(
342
+ paramNode,
343
+ parts,
344
+ partIndex + 1,
345
+ method,
346
+ params,
347
+ );
348
+ if (result) return result;
349
+ delete params[paramNode.paramName!];
350
+ }
351
+ } else {
352
+ params[paramNode.paramName!] = part;
353
+ const result = this.searchTree(
354
+ paramNode,
355
+ parts,
356
+ partIndex + 1,
357
+ method,
358
+ params,
359
+ );
360
+ if (result) return result;
361
+ delete params[paramNode.paramName!];
362
+ }
363
+ }
364
+
365
+ // 3. Try wildcard child - lowest priority
366
+ if (node.wildcardChild) {
367
+ const wildcardNode = node.wildcardChild;
368
+ // Capture remaining path
369
+ params["*"] = parts.slice(partIndex).join("/");
370
+
371
+ const handlerEntry =
372
+ wildcardNode.handlers.get(method) || wildcardNode.handlers.get("ALL");
373
+ if (handlerEntry) {
374
+ return {
375
+ handler: handlerEntry.handler,
376
+ params: { ...params },
377
+ middleware: handlerEntry.middleware,
378
+ name: handlerEntry.name,
379
+ };
380
+ }
381
+ }
382
+
383
+ return undefined;
384
+ }
385
+
386
+ /**
387
+ * Create a route group with prefix and optional middleware
388
+ */
389
+ group(
390
+ prefix: string,
391
+ options?: { middleware?: MiddlewareHandler | MiddlewareHandler[] },
392
+ ): TreeRouter {
393
+ const childRouter = new TreeRouter();
394
+
395
+ // Share root node
396
+ childRouter.root = this.root;
397
+ childRouter.groupPrefix = this.groupPrefix + prefix;
398
+
399
+ const optsMiddleware = options?.middleware;
400
+ const middlewareArray: MiddlewareHandler[] = optsMiddleware
401
+ ? Array.isArray(optsMiddleware)
402
+ ? optsMiddleware
403
+ : [optsMiddleware]
404
+ : [];
405
+
406
+ childRouter.groupMiddleware = [...this.groupMiddleware, ...middlewareArray];
407
+ childRouter.routeCount = this.routeCount;
408
+
409
+ return childRouter;
410
+ }
411
+
412
+ /**
413
+ * Get all registered routes (traverses tree)
414
+ */
415
+ getRoutes(): Array<{
416
+ method: HTTPMethod | "ALL";
417
+ pattern: string;
418
+ name?: string;
419
+ }> {
420
+ const routes: Array<{
421
+ method: HTTPMethod | "ALL";
422
+ pattern: string;
423
+ name?: string;
424
+ }> = [];
425
+
426
+ this.traverseTree(this.root, "", routes);
427
+
428
+ return routes;
429
+ }
430
+
431
+ /**
432
+ * Traverse tree to collect all routes
433
+ */
434
+ private traverseTree(
435
+ node: TreeNode,
436
+ currentPath: string,
437
+ routes: Array<{
438
+ method: HTTPMethod | "ALL";
439
+ pattern: string;
440
+ name?: string;
441
+ }>,
442
+ ): void {
443
+ // Add handlers at this node
444
+ for (const [method, entry] of node.handlers) {
445
+ routes.push({
446
+ method,
447
+ pattern: currentPath || "/",
448
+ name: entry.name,
449
+ });
450
+ }
451
+
452
+ // Traverse static children
453
+ for (const [path, child] of node.children) {
454
+ const childPath = `${currentPath}/${path}`;
455
+ this.traverseTree(child, childPath, routes);
456
+ }
457
+
458
+ // Traverse parameter child
459
+ if (node.paramChild) {
460
+ const childPath = `${currentPath}/:${node.paramChild.paramName}`;
461
+ this.traverseTree(node.paramChild, childPath, routes);
462
+ }
463
+
464
+ // Traverse wildcard child
465
+ if (node.wildcardChild) {
466
+ const childPath = `${currentPath}/*`;
467
+ this.traverseTree(node.wildcardChild, childPath, routes);
468
+ }
469
+ }
470
+
471
+ /**
472
+ * Get router type for debugging
473
+ */
474
+ getRouterType(): "tree" {
475
+ return "tree";
476
+ }
477
+
478
+ /**
479
+ * Get route count
480
+ */
481
+ getRouteCount(): number {
482
+ return this.routeCount;
483
+ }
484
+
485
+ /**
486
+ * Get tree statistics
487
+ */
488
+ getTreeStats(): { nodes: number; depth: number; routes: number } {
489
+ return {
490
+ nodes: this.countNodes(this.root),
491
+ depth: this.getTreeDepth(this.root),
492
+ routes: this.routeCount,
493
+ };
494
+ }
495
+
496
+ private countNodes(node: TreeNode): number {
497
+ let count = 1;
498
+
499
+ for (const child of node.children.values()) {
500
+ count += this.countNodes(child);
501
+ }
502
+
503
+ if (node.paramChild) {
504
+ count += this.countNodes(node.paramChild);
505
+ }
506
+
507
+ if (node.wildcardChild) {
508
+ count += this.countNodes(node.wildcardChild);
509
+ }
510
+
511
+ return count;
512
+ }
513
+
514
+ private getTreeDepth(node: TreeNode): number {
515
+ let maxDepth = 0;
516
+
517
+ for (const child of node.children.values()) {
518
+ maxDepth = Math.max(maxDepth, this.getTreeDepth(child));
519
+ }
520
+
521
+ if (node.paramChild) {
522
+ maxDepth = Math.max(maxDepth, this.getTreeDepth(node.paramChild));
523
+ }
524
+
525
+ if (node.wildcardChild) {
526
+ maxDepth = Math.max(maxDepth, this.getTreeDepth(node.wildcardChild));
527
+ }
528
+
529
+ return maxDepth + 1;
530
+ }
531
+
532
+ // ============= HTTP Method Helpers =============
533
+
534
+ get(pattern: string, handler: RouteHandler, options?: RouteOptions): void {
535
+ this.addRoute("GET", pattern, handler, options);
536
+ }
537
+ post(pattern: string, handler: RouteHandler, options?: RouteOptions): void {
538
+ this.addRoute("POST", pattern, handler, options);
539
+ }
540
+ put(pattern: string, handler: RouteHandler, options?: RouteOptions): void {
541
+ this.addRoute("PUT", pattern, handler, options);
542
+ }
543
+ patch(pattern: string, handler: RouteHandler, options?: RouteOptions): void {
544
+ this.addRoute("PATCH", pattern, handler, options);
545
+ }
546
+ delete(pattern: string, handler: RouteHandler, options?: RouteOptions): void {
547
+ this.addRoute("DELETE", pattern, handler, options);
548
+ }
549
+ head(pattern: string, handler: RouteHandler, options?: RouteOptions): void {
550
+ this.addRoute("HEAD", pattern, handler, options);
551
+ }
552
+ options(
553
+ pattern: string,
554
+ handler: RouteHandler,
555
+ options?: RouteOptions,
556
+ ): void {
557
+ this.addRoute("OPTIONS", pattern, handler, options);
558
+ }
559
+ all(pattern: string, handler: RouteHandler, options?: RouteOptions): void {
560
+ this.addRoute("ALL", pattern, handler, options);
561
+ }
562
+ }