@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,405 @@
1
+ /**
2
+ * Guards System
3
+ *
4
+ * Guards determine whether a request should be allowed to proceed to the handler.
5
+ * They run before interceptors and pipes in the request pipeline.
6
+ *
7
+ * Execution Order:
8
+ * Incoming Request → Guards → Interceptors → Pipes → Handler
9
+ *
10
+ * If any guard returns false, the request is rejected with 403 Forbidden.
11
+ */
12
+
13
+ import type { Context } from "../context";
14
+ import type { Token } from "../container";
15
+
16
+ // ============= Types =============
17
+
18
+ /**
19
+ * Guard interface for authorization checks
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * @Injectable()
24
+ * class AuthGuard implements CanActivate {
25
+ * canActivate(context: Context): boolean {
26
+ * return !!context.request.headers.get('Authorization');
27
+ * }
28
+ * }
29
+ * ```
30
+ */
31
+ export interface CanActivate {
32
+ canActivate(context: Context): boolean | Promise<boolean>;
33
+ }
34
+
35
+ /**
36
+ * Guard function type (for functional guards)
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * const authGuard: GuardFn = (context) => {
41
+ * return !!context.request.headers.get('Authorization');
42
+ * };
43
+ * ```
44
+ */
45
+ export type GuardFn = (context: Context) => boolean | Promise<boolean>;
46
+
47
+ /**
48
+ * Guard type - can be:
49
+ * - A token for a guard class registered in the container
50
+ * - A guard class instance
51
+ * - A guard function
52
+ */
53
+ export type Guard = Token<CanActivate> | CanActivate | GuardFn;
54
+
55
+ /**
56
+ * Metadata key for storing guards on classes and methods
57
+ */
58
+ const GUARDS_METADATA_KEY = "guards";
59
+
60
+ // ============= Metadata Storage =============
61
+
62
+ // Type alias for class constructors
63
+ type Constructor = new (...args: unknown[]) => unknown;
64
+
65
+ // WeakMap for storing guards metadata on classes
66
+ const guardsClassMetadata = new WeakMap<Constructor, Guard[]>();
67
+
68
+ // WeakMap for storing guards metadata on method prototypes
69
+ const guardsMethodMetadata = new WeakMap<object, Map<string | symbol, Guard[]>>();
70
+
71
+ /**
72
+ * Set guards on a class constructor
73
+ */
74
+ function setClassGuards(target: Constructor, guards: Guard[]): void {
75
+ guardsClassMetadata.set(target, guards);
76
+ }
77
+
78
+ /**
79
+ * Get guards from a class constructor
80
+ */
81
+ export function getClassGuards(target: Constructor): Guard[] | undefined {
82
+ return guardsClassMetadata.get(target);
83
+ }
84
+
85
+ /**
86
+ * Set guards on a method
87
+ */
88
+ function setMethodGuards(
89
+ target: object,
90
+ propertyKey: string | symbol,
91
+ guards: Guard[],
92
+ ): void {
93
+ if (!guardsMethodMetadata.has(target)) {
94
+ guardsMethodMetadata.set(target, new Map());
95
+ }
96
+ guardsMethodMetadata.get(target)?.set(propertyKey, guards);
97
+ }
98
+
99
+ /**
100
+ * Get guards from a method
101
+ */
102
+ export function getMethodGuards(
103
+ target: object,
104
+ propertyKey: string | symbol,
105
+ ): Guard[] | undefined {
106
+ return guardsMethodMetadata.get(target)?.get(propertyKey);
107
+ }
108
+
109
+ // ============= Decorators =============
110
+
111
+ /**
112
+ * Decorator to apply guards to a controller class or method.
113
+ * Guards are executed in the order they are provided.
114
+ *
115
+ * @param guards - Guards to apply
116
+ * @returns ClassDecorator & MethodDecorator
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * // Apply to all methods in controller
121
+ * @Controller('users')
122
+ * @UseGuards(AuthGuard)
123
+ * class UsersController {
124
+ * @Get()
125
+ * getUsers() {} // Protected by AuthGuard
126
+ *
127
+ * @Get(':id')
128
+ * @UseGuards(RolesGuard) // Additional guard
129
+ * getUser() {} // Protected by AuthGuard AND RolesGuard
130
+ * }
131
+ * ```
132
+ */
133
+ export function UseGuards(...guards: Guard[]): MethodDecorator & ClassDecorator {
134
+ const decorator = (
135
+ target: unknown,
136
+ propertyKey?: string | symbol,
137
+ descriptor?: PropertyDescriptor,
138
+ ): PropertyDescriptor | void => {
139
+ if (propertyKey !== undefined && descriptor !== undefined) {
140
+ // Method decorator
141
+ const targetObj = target as object;
142
+ const existingGuards = getMethodGuards(targetObj, propertyKey) ?? [];
143
+ setMethodGuards(targetObj, propertyKey, [...existingGuards, ...guards]);
144
+ return descriptor;
145
+ } else {
146
+ // Class decorator
147
+ const targetClass = target as Constructor;
148
+ const existingGuards = getClassGuards(targetClass) ?? [];
149
+ setClassGuards(targetClass, [...existingGuards, ...guards]);
150
+ }
151
+ };
152
+ return decorator as MethodDecorator & ClassDecorator;
153
+ }
154
+
155
+ // ============= Built-in Guards =============
156
+
157
+ /**
158
+ * AuthGuard - Checks for Authorization header
159
+ *
160
+ * This guard verifies that the request has an Authorization header.
161
+ * It does not validate the token - that should be done by a custom guard.
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * @Controller('api')
166
+ * @UseGuards(AuthGuard)
167
+ * class ApiController {
168
+ * @Get('protected')
169
+ * protectedRoute() {} // Requires Authorization header
170
+ * }
171
+ * ```
172
+ */
173
+ export class AuthGuard implements CanActivate {
174
+ canActivate(context: Context): boolean {
175
+ const authHeader = context.req.headers.get("Authorization");
176
+ return authHeader !== null && authHeader.length > 0;
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Role metadata key for storing required roles on methods
182
+ */
183
+ const ROLES_METADATA_KEY = "roles";
184
+
185
+ // WeakMap for storing roles metadata on method prototypes
186
+ const rolesMethodMetadata = new WeakMap<object, Map<string | symbol, string[]>>();
187
+
188
+ /**
189
+ * Set required roles on a method
190
+ */
191
+ function setMethodRoles(
192
+ target: object,
193
+ propertyKey: string | symbol,
194
+ roles: string[],
195
+ ): void {
196
+ if (!rolesMethodMetadata.has(target)) {
197
+ rolesMethodMetadata.set(target, new Map());
198
+ }
199
+ rolesMethodMetadata.get(target)?.set(propertyKey, roles);
200
+ }
201
+
202
+ /**
203
+ * Get required roles from a method
204
+ */
205
+ export function getMethodRoles(
206
+ target: object,
207
+ propertyKey: string | symbol,
208
+ ): string[] | undefined {
209
+ return rolesMethodMetadata.get(target)?.get(propertyKey);
210
+ }
211
+
212
+ /**
213
+ * Decorator to specify required roles for a route
214
+ * Must be used in conjunction with RolesGuard
215
+ *
216
+ * @param roles - Required roles
217
+ * @returns MethodDecorator
218
+ *
219
+ * @example
220
+ * ```typescript
221
+ * @Controller('admin')
222
+ * @UseGuards(AuthGuard, RolesGuard)
223
+ * class AdminController {
224
+ * @Get('users')
225
+ * @Roles('admin', 'moderator')
226
+ * getUsers() {} // Requires 'admin' or 'moderator' role
227
+ * }
228
+ * ```
229
+ */
230
+ export function Roles(...roles: string[]): MethodDecorator {
231
+ return (
232
+ target: unknown,
233
+ propertyKey: string | symbol,
234
+ descriptor: PropertyDescriptor,
235
+ ): PropertyDescriptor => {
236
+ const targetObj = target as object;
237
+ setMethodRoles(targetObj, propertyKey, roles);
238
+ return descriptor;
239
+ };
240
+ }
241
+
242
+ /**
243
+ * User interface for type safety
244
+ * Applications should extend this interface with their user type
245
+ */
246
+ export interface User {
247
+ id: string | number;
248
+ roles?: string[];
249
+ }
250
+
251
+ /**
252
+ * Context extension for user data
253
+ * This extends the Context type to include user information
254
+ */
255
+ declare module "../context" {
256
+ interface Context {
257
+ user?: User;
258
+ }
259
+ }
260
+
261
+ /**
262
+ * RolesGuard - Checks user roles
263
+ *
264
+ * This guard checks if the authenticated user has the required roles.
265
+ * It should be used after AuthGuard in the guard chain.
266
+ *
267
+ * The user object must be set on the context before this guard runs.
268
+ * This is typically done by an authentication middleware or a previous guard.
269
+ *
270
+ * @example
271
+ * ```typescript
272
+ * @Controller('admin')
273
+ * @UseGuards(AuthGuard, RolesGuard)
274
+ * class AdminController {
275
+ * @Get('dashboard')
276
+ * @Roles('admin')
277
+ * getDashboard() {} // Requires 'admin' role
278
+ * }
279
+ * ```
280
+ */
281
+ export class RolesGuard implements CanActivate {
282
+ canActivate(context: Context): boolean {
283
+ // Get required roles from context (set by the framework during route matching)
284
+ const requiredRoles = (context as unknown as { requiredRoles?: string[] }).requiredRoles;
285
+
286
+ // If no roles are required, allow access
287
+ if (!requiredRoles || requiredRoles.length === 0) {
288
+ return true;
289
+ }
290
+
291
+ // Check if user exists and has at least one required role
292
+ const user = (context as unknown as { user?: User }).user;
293
+ if (!user || !user.roles) {
294
+ return false;
295
+ }
296
+
297
+ return requiredRoles.some((role) => user.roles?.includes(role));
298
+ }
299
+ }
300
+
301
+ // ============= Guard Executor =============
302
+
303
+ /**
304
+ * Guard executor options
305
+ */
306
+ export interface GuardExecutorOptions {
307
+ /** Global guards applied to all routes */
308
+ globalGuards?: Guard[];
309
+ /** Guards from controller class */
310
+ classGuards?: Guard[];
311
+ /** Guards from method */
312
+ methodGuards?: Guard[];
313
+ /** Container for resolving guard instances */
314
+ resolveGuard?: (guard: Guard) => CanActivate | GuardFn | null;
315
+ }
316
+
317
+ /**
318
+ * Execute guards in order and return whether the request should proceed
319
+ *
320
+ * @param context - Request context
321
+ * @param options - Guard executor options
322
+ * @returns true if all guards pass, false otherwise
323
+ */
324
+ export async function executeGuards(
325
+ context: Context,
326
+ options: GuardExecutorOptions,
327
+ ): Promise<boolean> {
328
+ const { globalGuards = [], classGuards = [], methodGuards = [], resolveGuard } = options;
329
+
330
+ // Combine all guards in execution order
331
+ const allGuards = [...globalGuards, ...classGuards, ...methodGuards];
332
+
333
+ // Execute each guard in order
334
+ for (const guard of allGuards) {
335
+ let guardInstance: CanActivate | GuardFn | null = null;
336
+
337
+ // Resolve the guard
338
+ if (typeof guard === "function") {
339
+ // Check if it's a guard function or a class constructor
340
+ const funcGuard = guard as { prototype?: unknown; canActivate?: unknown };
341
+ if (funcGuard.prototype && typeof funcGuard.prototype === "object" &&
342
+ "canActivate" in (funcGuard.prototype as object)) {
343
+ // It's a class constructor - try to resolve from container or create instance
344
+ guardInstance = resolveGuard ? resolveGuard(guard) : null;
345
+ if (!guardInstance) {
346
+ // Create a new instance if not in container
347
+ // Use unknown first to safely convert
348
+ const GuardClass = guard as unknown as new () => CanActivate;
349
+ guardInstance = new GuardClass();
350
+ }
351
+ } else {
352
+ // It's a guard function
353
+ guardInstance = guard as GuardFn;
354
+ }
355
+ } else if (typeof guard === "object" && guard !== null) {
356
+ // It's a token or already an instance
357
+ const objGuard = guard as { canActivate?: unknown };
358
+ if ("canActivate" in objGuard && typeof objGuard.canActivate === "function") {
359
+ // It's already a CanActivate instance
360
+ guardInstance = guard as CanActivate;
361
+ } else {
362
+ // It's a token - try to resolve
363
+ guardInstance = resolveGuard ? resolveGuard(guard) : null;
364
+ }
365
+ }
366
+
367
+ if (!guardInstance) {
368
+ console.warn("Guard could not be resolved:", guard);
369
+ continue;
370
+ }
371
+
372
+ // Execute the guard
373
+ let result: boolean;
374
+ if (typeof guardInstance === "function") {
375
+ // Guard function
376
+ result = await guardInstance(context);
377
+ } else {
378
+ // CanActivate instance
379
+ result = await guardInstance.canActivate(context);
380
+ }
381
+
382
+ // If any guard returns false, stop and return false
383
+ if (!result) {
384
+ return false;
385
+ }
386
+ }
387
+
388
+ return true;
389
+ }
390
+
391
+ /**
392
+ * Create a 403 Forbidden response
393
+ */
394
+ export function createForbiddenResponse(): Response {
395
+ return new Response(JSON.stringify({
396
+ statusCode: 403,
397
+ error: "Forbidden",
398
+ message: "Access denied",
399
+ }), {
400
+ status: 403,
401
+ headers: {
402
+ "Content-Type": "application/json",
403
+ },
404
+ });
405
+ }