@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,444 @@
1
+ /**
2
+ * Structured Logging System
3
+ *
4
+ * Provides structured logging with JSON output, log levels,
5
+ * context-aware logging, and performance metrics.
6
+ */
7
+
8
+ // ============= Types =============
9
+
10
+ export type LogLevel = "debug" | "info" | "warn" | "error" | "fatal";
11
+
12
+ export interface LogEntry {
13
+ level: LogLevel;
14
+ message: string;
15
+ timestamp: string;
16
+ context?: Record<string, unknown>;
17
+ error?: {
18
+ name: string;
19
+ message: string;
20
+ stack?: string;
21
+ };
22
+ duration?: number;
23
+ [key: string]: unknown;
24
+ }
25
+
26
+ export interface LoggerConfig {
27
+ level?: LogLevel;
28
+ pretty?: boolean;
29
+ timestamp?: boolean;
30
+ context?: Record<string, unknown>;
31
+ output?: "console" | "stdout" | ((entry: LogEntry) => void);
32
+ }
33
+
34
+ export interface LoggerContext {
35
+ requestId?: string;
36
+ userId?: string;
37
+ method?: string;
38
+ path?: string;
39
+ [key: string]: unknown;
40
+ }
41
+
42
+ // ============= Log Level Priority =============
43
+
44
+ const LOG_LEVELS: Record<LogLevel, number> = {
45
+ debug: 0,
46
+ info: 1,
47
+ warn: 2,
48
+ error: 3,
49
+ fatal: 4,
50
+ };
51
+
52
+ // ============= Logger Class =============
53
+
54
+ export class Logger {
55
+ private level: LogLevel;
56
+ private pretty: boolean;
57
+ private timestamp: boolean;
58
+ private context: Record<string, unknown>;
59
+ private output: (entry: LogEntry) => void;
60
+
61
+ constructor(config: LoggerConfig = {}) {
62
+ this.level = config.level ?? "info";
63
+ this.pretty = config.pretty ?? process.env.NODE_ENV !== "production";
64
+ this.timestamp = config.timestamp ?? true;
65
+ this.context = config.context ?? {};
66
+
67
+ if (typeof config.output === "function") {
68
+ this.output = config.output;
69
+ } else if (config.output === "stdout") {
70
+ this.output = (entry) => console.log(this.serialize(entry));
71
+ } else {
72
+ this.output = (entry) => {
73
+ if (this.pretty) {
74
+ this.prettyPrint(entry);
75
+ } else {
76
+ console.log(this.serialize(entry));
77
+ }
78
+ };
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Check if a log level should be logged
84
+ */
85
+ private shouldLog(level: LogLevel): boolean {
86
+ return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
87
+ }
88
+
89
+ /**
90
+ * Serialize log entry to JSON
91
+ */
92
+ private serialize(entry: LogEntry): string {
93
+ return JSON.stringify(entry);
94
+ }
95
+
96
+ /**
97
+ * Pretty print log entry
98
+ */
99
+ private prettyPrint(entry: LogEntry): void {
100
+ const timestamp = entry.timestamp;
101
+ const levelColors: Record<LogLevel, string> = {
102
+ debug: "\x1b[36m", // cyan
103
+ info: "\x1b[32m", // green
104
+ warn: "\x1b[33m", // yellow
105
+ error: "\x1b[31m", // red
106
+ fatal: "\x1b[35m", // magenta
107
+ };
108
+ const reset = "\x1b[0m";
109
+ const color = levelColors[entry.level];
110
+
111
+ let output = `${timestamp} ${color}[${entry.level.toUpperCase()}]${reset} ${entry.message}`;
112
+
113
+ if (entry.context && Object.keys(entry.context).length > 0) {
114
+ output += ` ${reset}\x1b[90m${JSON.stringify(entry.context)}\x1b[0m`;
115
+ }
116
+
117
+ if (entry.duration !== undefined) {
118
+ output += ` \x1b[90m(${entry.duration}ms)\x1b[0m`;
119
+ }
120
+
121
+ if (entry.error) {
122
+ output += `\n \x1b[31m${entry.error.name}: ${entry.error.message}\x1b[0m`;
123
+ if (entry.error.stack) {
124
+ output += `\n \x1b[90m${entry.error.stack}\x1b[0m`;
125
+ }
126
+ }
127
+
128
+ switch (entry.level) {
129
+ case "error":
130
+ case "fatal":
131
+ console.error(output);
132
+ break;
133
+ case "warn":
134
+ console.warn(output);
135
+ break;
136
+ default:
137
+ console.log(output);
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Create a log entry
143
+ */
144
+ private createEntry(
145
+ level: LogLevel,
146
+ message: string,
147
+ context?: Record<string, unknown>,
148
+ error?: Error,
149
+ ): LogEntry {
150
+ const entry: LogEntry = {
151
+ level,
152
+ message,
153
+ timestamp: this.timestamp ? new Date().toISOString() : "",
154
+ ...this.context,
155
+ };
156
+
157
+ if (context) {
158
+ entry.context = context;
159
+ }
160
+
161
+ if (error) {
162
+ entry.error = {
163
+ name: error.name,
164
+ message: error.message,
165
+ stack: error.stack,
166
+ };
167
+ }
168
+
169
+ return entry;
170
+ }
171
+
172
+ /**
173
+ * Log a debug message
174
+ */
175
+ debug(message: string, context?: Record<string, unknown>): void {
176
+ if (!this.shouldLog("debug")) return;
177
+ this.output(this.createEntry("debug", message, context));
178
+ }
179
+
180
+ /**
181
+ * Log an info message
182
+ */
183
+ info(message: string, context?: Record<string, unknown>): void {
184
+ if (!this.shouldLog("info")) return;
185
+ this.output(this.createEntry("info", message, context));
186
+ }
187
+
188
+ /**
189
+ * Log a warning message
190
+ */
191
+ warn(message: string, context?: Record<string, unknown>): void {
192
+ if (!this.shouldLog("warn")) return;
193
+ this.output(this.createEntry("warn", message, context));
194
+ }
195
+
196
+ /**
197
+ * Log an error message
198
+ */
199
+ error(
200
+ message: string,
201
+ error?: Error | unknown,
202
+ context?: Record<string, unknown>,
203
+ ): void {
204
+ if (!this.shouldLog("error")) return;
205
+ const err = error instanceof Error ? error : undefined;
206
+ this.output(this.createEntry("error", message, context, err));
207
+ }
208
+
209
+ /**
210
+ * Log a fatal message
211
+ */
212
+ fatal(
213
+ message: string,
214
+ error?: Error | unknown,
215
+ context?: Record<string, unknown>,
216
+ ): void {
217
+ if (!this.shouldLog("fatal")) return;
218
+ const err = error instanceof Error ? error : undefined;
219
+ this.output(this.createEntry("fatal", message, context, err));
220
+ }
221
+
222
+ /**
223
+ * Create a child logger with additional context
224
+ */
225
+ child(context: Record<string, unknown>): Logger {
226
+ return new Logger({
227
+ level: this.level,
228
+ pretty: this.pretty,
229
+ timestamp: this.timestamp,
230
+ context: { ...this.context, ...context },
231
+ output: this.output,
232
+ });
233
+ }
234
+
235
+ /**
236
+ * Set log level
237
+ */
238
+ setLevel(level: LogLevel): void {
239
+ this.level = level;
240
+ }
241
+
242
+ /**
243
+ * Add context to the logger
244
+ */
245
+ addContext(context: Record<string, unknown>): void {
246
+ Object.assign(this.context, context);
247
+ }
248
+
249
+ /**
250
+ * Time a function
251
+ */
252
+ async time<T>(label: string, fn: () => T | Promise<T>): Promise<T> {
253
+ const start = Date.now();
254
+
255
+ try {
256
+ const result = await fn();
257
+ this.debug(`${label} completed`, { duration: Date.now() - start });
258
+ return result;
259
+ } catch (error) {
260
+ this.error(`${label} failed`, error, { duration: Date.now() - start });
261
+ throw error;
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Create a timer
267
+ */
268
+ startTimer(label: string): () => number {
269
+ const start = Date.now();
270
+ return () => {
271
+ const duration = Date.now() - start;
272
+ this.debug(`${label}`, { duration });
273
+ return duration;
274
+ };
275
+ }
276
+ }
277
+
278
+ // ============= Request Logger =============
279
+
280
+ export interface RequestLogContext {
281
+ requestId?: string;
282
+ method: string;
283
+ path: string;
284
+ query?: string;
285
+ ip?: string;
286
+ userAgent?: string;
287
+ userId?: string;
288
+ }
289
+
290
+ export interface ResponseLogContext extends RequestLogContext {
291
+ statusCode: number;
292
+ duration: number;
293
+ contentLength?: number;
294
+ }
295
+
296
+ /**
297
+ * Create a request logger middleware
298
+ */
299
+ export function createRequestLogger(logger: Logger) {
300
+ return async (ctx: unknown, next: () => Promise<unknown>) => {
301
+ const context = ctx as {
302
+ method: string;
303
+ path: string;
304
+ url: URL;
305
+ getHeader: (name: string) => string | undefined;
306
+ ip?: string;
307
+ set: (key: string, value: unknown) => void;
308
+ get: (key: string) => unknown;
309
+ };
310
+ const start = Date.now();
311
+ const requestId = context.getHeader("x-request-id") || crypto.randomUUID();
312
+
313
+ context.set("requestId", requestId);
314
+
315
+ // Log request
316
+ const requestContext: Record<string, unknown> = {
317
+ requestId,
318
+ method: context.method,
319
+ path: context.path,
320
+ query: context.url?.search,
321
+ ip: context.ip,
322
+ userAgent: context.getHeader("user-agent"),
323
+ };
324
+
325
+ logger.info("Request started", requestContext);
326
+
327
+ try {
328
+ const response = await next();
329
+
330
+ // Log response
331
+ const duration = Date.now() - start;
332
+ const responseContext: Record<string, unknown> = {
333
+ ...requestContext,
334
+ statusCode: (response as Response)?.status ?? 200,
335
+ duration,
336
+ };
337
+
338
+ logger.info("Request completed", responseContext);
339
+
340
+ return response;
341
+ } catch (error) {
342
+ const duration = Date.now() - start;
343
+ logger.error("Request failed", error, {
344
+ ...requestContext,
345
+ duration,
346
+ });
347
+ throw error;
348
+ }
349
+ };
350
+ }
351
+
352
+ // ============= Performance Logger =============
353
+
354
+ export class PerformanceLogger {
355
+ private logger: Logger;
356
+ private metrics: Map<string, number[]> = new Map();
357
+
358
+ constructor(logger: Logger) {
359
+ this.logger = logger;
360
+ }
361
+
362
+ /**
363
+ * Record a metric
364
+ */
365
+ record(name: string, value: number): void {
366
+ if (!this.metrics.has(name)) {
367
+ this.metrics.set(name, []);
368
+ }
369
+ this.metrics.get(name)?.push(value);
370
+ }
371
+
372
+ /**
373
+ * Get metric statistics
374
+ */
375
+ stats(
376
+ name: string,
377
+ ): {
378
+ count: number;
379
+ min: number;
380
+ max: number;
381
+ avg: number;
382
+ p99: number;
383
+ } | null {
384
+ const values = this.metrics.get(name);
385
+ if (!values || values.length === 0) return null;
386
+
387
+ const sorted = [...values].sort((a, b) => a - b);
388
+ const count = sorted.length;
389
+ const min = sorted[0];
390
+ const max = sorted[count - 1];
391
+ const avg = sorted.reduce((a, b) => a + b, 0) / count;
392
+ const p99Index = Math.floor(count * 0.99);
393
+ const p99 = sorted[p99Index];
394
+
395
+ return { count, min, max, avg, p99 };
396
+ }
397
+
398
+ /**
399
+ * Log all metrics
400
+ */
401
+ logMetrics(): void {
402
+ for (const [name] of this.metrics) {
403
+ const stats = this.stats(name);
404
+ if (stats) {
405
+ this.logger.info(`Metric: ${name}`, stats);
406
+ }
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Clear all metrics
412
+ */
413
+ clear(): void {
414
+ this.metrics.clear();
415
+ }
416
+ }
417
+
418
+ // ============= Default Logger Instance =============
419
+
420
+ let defaultLogger: Logger | null = null;
421
+
422
+ /**
423
+ * Get the default logger instance
424
+ */
425
+ export function getLogger(): Logger {
426
+ if (!defaultLogger) {
427
+ defaultLogger = new Logger();
428
+ }
429
+ return defaultLogger;
430
+ }
431
+
432
+ /**
433
+ * Set the default logger instance
434
+ */
435
+ export function setLogger(logger: Logger): void {
436
+ defaultLogger = logger;
437
+ }
438
+
439
+ /**
440
+ * Create a new logger
441
+ */
442
+ export function createLogger(config?: LoggerConfig): Logger {
443
+ return new Logger(config);
444
+ }