@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,506 @@
1
+ /**
2
+ * Configuration System for Bueno Framework
3
+ *
4
+ * Provides a comprehensive configuration system supporting:
5
+ * - `bueno.config.ts` file loading
6
+ * - Environment variables
7
+ * - Schema validation
8
+ * - Deep merging of config sources
9
+ */
10
+
11
+ // Re-export types
12
+ export type {
13
+ BuenoConfig,
14
+ ServerConfig,
15
+ DatabaseConfig,
16
+ CacheConfig,
17
+ LoggerConfig,
18
+ HealthConfig,
19
+ MetricsConfig,
20
+ TelemetryConfig,
21
+ FrontendConfig,
22
+ DeepPartial,
23
+ UserConfig,
24
+ UserConfigFn,
25
+ InferConfig,
26
+ } from "./types";
27
+
28
+ // Re-export merge utilities
29
+ export { deepMerge, mergeConfigs, isObject } from "./merge";
30
+
31
+ // Re-export environment utilities
32
+ export {
33
+ loadEnv,
34
+ getEnvConfig,
35
+ getEnvValue,
36
+ setEnvValue,
37
+ envConfigMapping,
38
+ type EnvConfigMapping,
39
+ } from "./env";
40
+
41
+ // Re-export loader utilities
42
+ export {
43
+ loadConfig,
44
+ loadConfigFile,
45
+ loadConfigFiles,
46
+ findConfigFile,
47
+ clearConfigCache,
48
+ getCachedConfig,
49
+ watchConfig,
50
+ validateConfigStructure,
51
+ getConfigPathFromArgs,
52
+ getConfigPathFromEnv,
53
+ type LoadedConfig,
54
+ } from "./loader";
55
+
56
+ // Re-export validation utilities
57
+ export {
58
+ validateConfig,
59
+ validateConfigSync,
60
+ validateConfigDefaults,
61
+ validateWithSchema,
62
+ assertValidConfig,
63
+ formatValidationErrors,
64
+ createConfigError,
65
+ createCustomValidator,
66
+ isStandardSchema,
67
+ type ConfigValidationResult,
68
+ type ConfigValidationError,
69
+ type ConfigValidationWarning,
70
+ } from "./validation";
71
+
72
+ import type { StandardSchema } from "../types";
73
+ import type {
74
+ BuenoConfig,
75
+ DeepPartial,
76
+ UserConfig,
77
+ UserConfigFn,
78
+ InferConfig,
79
+ } from "./types";
80
+ import { deepMerge, mergeConfigs } from "./merge";
81
+ import { loadEnv, getEnvConfig } from "./env";
82
+ import { loadConfig, findConfigFile, clearConfigCache } from "./loader";
83
+ import {
84
+ validateConfig,
85
+ validateConfigSync,
86
+ assertValidConfig,
87
+ type ConfigValidationResult,
88
+ } from "./validation";
89
+
90
+ /**
91
+ * Default configuration values
92
+ */
93
+ const DEFAULT_CONFIG: BuenoConfig = {
94
+ server: {
95
+ port: 3000,
96
+ host: "localhost",
97
+ development: false,
98
+ },
99
+ database: {
100
+ url: undefined,
101
+ poolSize: 10,
102
+ enableMetrics: true,
103
+ slowQueryThreshold: 100,
104
+ },
105
+ cache: {
106
+ driver: "memory",
107
+ url: undefined,
108
+ ttl: 3600,
109
+ keyPrefix: "",
110
+ enableMetrics: true,
111
+ },
112
+ logger: {
113
+ level: "info",
114
+ pretty: false,
115
+ output: "console",
116
+ },
117
+ health: {
118
+ enabled: true,
119
+ healthPath: "/health",
120
+ readyPath: "/ready",
121
+ },
122
+ metrics: {
123
+ enabled: true,
124
+ collectInterval: 60000,
125
+ maxHistorySize: 100,
126
+ },
127
+ telemetry: {
128
+ enabled: false,
129
+ serviceName: "bueno-app",
130
+ endpoint: undefined,
131
+ sampleRate: 1.0,
132
+ },
133
+ frontend: {
134
+ devServer: false,
135
+ hmr: true,
136
+ port: 3001,
137
+ },
138
+ };
139
+
140
+ /**
141
+ * ConfigManager options
142
+ */
143
+ export interface ConfigManagerOptions<T extends BuenoConfig = BuenoConfig> {
144
+ /** Custom config file path */
145
+ configPath?: string;
146
+ /** Working directory */
147
+ cwd?: string;
148
+ /** Whether to load environment variables */
149
+ loadEnv?: boolean;
150
+ /** Whether to use default config */
151
+ useDefaults?: boolean;
152
+ /** Custom schema for validation */
153
+ schema?: StandardSchema<T>;
154
+ /** Whether to validate config on load */
155
+ validate?: boolean;
156
+ /** Additional config to merge */
157
+ config?: DeepPartial<T>;
158
+ }
159
+
160
+ /**
161
+ * Config change callback
162
+ */
163
+ export type ConfigChangeCallback<T extends BuenoConfig = BuenoConfig> = (
164
+ config: T,
165
+ ) => void;
166
+
167
+ /**
168
+ * ConfigManager class
169
+ * Manages configuration loading, merging, validation, and access
170
+ */
171
+ export class ConfigManager<T extends BuenoConfig = BuenoConfig> {
172
+ private config: T;
173
+ private options: ConfigManagerOptions<T>;
174
+ private loaded = false;
175
+ private filePath?: string;
176
+ private watchers: ConfigChangeCallback<T>[] = [];
177
+ private unwatch?: () => void;
178
+
179
+ constructor(options: ConfigManagerOptions<T> = {}) {
180
+ this.options = options;
181
+ this.config = (options.useDefaults !== false
182
+ ? { ...DEFAULT_CONFIG }
183
+ : {}) as T;
184
+
185
+ // Apply initial config if provided
186
+ if (options.config) {
187
+ this.config = deepMerge(this.config, options.config) as T;
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Load configuration from all sources
193
+ * Order: defaults → file config → env vars → CLI args → provided config
194
+ */
195
+ async load(): Promise<T> {
196
+ // Load environment variables
197
+ if (this.options.loadEnv !== false) {
198
+ loadEnv();
199
+ }
200
+
201
+ // Load config file
202
+ const { config: fileConfig, filePath } = await loadConfig<T>({
203
+ configPath: this.options.configPath,
204
+ cwd: this.options.cwd,
205
+ });
206
+
207
+ this.filePath = filePath;
208
+
209
+ // Get environment config
210
+ const envConfig = getEnvConfig();
211
+
212
+ // Merge all sources
213
+ const merged = mergeConfigs(
214
+ {} as T,
215
+ this.options.useDefaults !== false ? DEFAULT_CONFIG : {} as T,
216
+ fileConfig as T,
217
+ envConfig as T,
218
+ this.options.config as T || {} as T,
219
+ );
220
+
221
+ this.config = merged as T;
222
+
223
+ // Validate if requested
224
+ if (this.options.validate !== false) {
225
+ await this.validate(this.options.schema);
226
+ }
227
+
228
+ this.loaded = true;
229
+ return this.config;
230
+ }
231
+
232
+ /**
233
+ * Get the entire configuration
234
+ */
235
+ getAll(): T {
236
+ return { ...this.config } as T;
237
+ }
238
+
239
+ /**
240
+ * Get a configuration value using dot notation
241
+ * @example
242
+ * config.get('server.port') // 3000
243
+ * config.get('database.url') // 'postgresql://...'
244
+ */
245
+ get<K extends keyof T>(key: K): T[K];
246
+ get<K extends string>(key: K): unknown;
247
+ get(key: string): unknown {
248
+ const parts = key.split(".");
249
+ let current: unknown = this.config;
250
+
251
+ for (const part of parts) {
252
+ if (current === null || current === undefined) {
253
+ return undefined;
254
+ }
255
+ if (typeof current !== "object") {
256
+ return undefined;
257
+ }
258
+ current = (current as Record<string, unknown>)[part];
259
+ }
260
+
261
+ return current;
262
+ }
263
+
264
+ /**
265
+ * Set a configuration value at runtime
266
+ * @example
267
+ * config.set('server.port', 4000)
268
+ */
269
+ set<K extends keyof T>(key: K, value: T[K]): void;
270
+ set(key: string, value: unknown): void;
271
+ set(key: string, value: unknown): void {
272
+ const parts = key.split(".");
273
+ let current: Record<string, unknown> = this.config as Record<string, unknown>;
274
+
275
+ for (let i = 0; i < parts.length - 1; i++) {
276
+ const part = parts[i];
277
+ if (!(part in current)) {
278
+ current[part] = {};
279
+ }
280
+ current = current[part] as Record<string, unknown>;
281
+ }
282
+
283
+ current[parts[parts.length - 1]] = value;
284
+
285
+ // Notify watchers
286
+ this.notifyWatchers();
287
+ }
288
+
289
+ /**
290
+ * Check if a configuration key exists
291
+ */
292
+ has(key: string): boolean {
293
+ return this.get(key) !== undefined;
294
+ }
295
+
296
+ /**
297
+ * Validate the current configuration
298
+ */
299
+ async validate(schema?: StandardSchema<T>): Promise<ConfigValidationResult> {
300
+ const result = await validateConfig(this.config, schema);
301
+
302
+ if (!result.valid) {
303
+ const errors = result.errors
304
+ .map((e) => `${e.path ? e.path + ": " : ""}${e.message}`)
305
+ .join("\n");
306
+ throw new Error(`Configuration validation failed:\n${errors}`);
307
+ }
308
+
309
+ // Log warnings
310
+ for (const warning of result.warnings) {
311
+ console.warn(
312
+ `Config warning: ${warning.message}${warning.path ? ` (at ${warning.path})` : ""}`,
313
+ );
314
+ }
315
+
316
+ return result;
317
+ }
318
+
319
+ /**
320
+ * Validate synchronously (only default rules)
321
+ */
322
+ validateSync(): ConfigValidationResult {
323
+ return validateConfigSync(this.config as unknown as DeepPartial<BuenoConfig>);
324
+ }
325
+
326
+ /**
327
+ * Watch for configuration changes
328
+ */
329
+ watch(callback: ConfigChangeCallback<T>): () => void {
330
+ this.watchers.push(callback);
331
+
332
+ // Return unsubscribe function
333
+ return () => {
334
+ const index = this.watchers.indexOf(callback);
335
+ if (index !== -1) {
336
+ this.watchers.splice(index, 1);
337
+ }
338
+ };
339
+ }
340
+
341
+ /**
342
+ * Start watching the config file for changes
343
+ */
344
+ async watchFile(): Promise<void> {
345
+ if (!this.filePath) {
346
+ this.filePath = await findConfigFile(this.options.cwd);
347
+ }
348
+
349
+ if (!this.filePath) {
350
+ return;
351
+ }
352
+
353
+ // Clear the cache to allow reloading
354
+ clearConfigCache();
355
+
356
+ // Set up polling for file changes
357
+ const { watchConfig } = await import("./loader");
358
+ this.unwatch = watchConfig(
359
+ this.filePath,
360
+ async () => {
361
+ await this.load();
362
+ },
363
+ { debounce: 100 },
364
+ );
365
+ }
366
+
367
+ /**
368
+ * Stop watching the config file
369
+ */
370
+ unwatchFile(): void {
371
+ if (this.unwatch) {
372
+ this.unwatch();
373
+ this.unwatch = undefined;
374
+ }
375
+ }
376
+
377
+ /**
378
+ * Check if config has been loaded
379
+ */
380
+ isLoaded(): boolean {
381
+ return this.loaded;
382
+ }
383
+
384
+ /**
385
+ * Get the config file path
386
+ */
387
+ getFilePath(): string | undefined {
388
+ return this.filePath;
389
+ }
390
+
391
+ /**
392
+ * Reset configuration to defaults
393
+ */
394
+ reset(): void {
395
+ this.config = { ...DEFAULT_CONFIG } as T;
396
+ this.loaded = false;
397
+ this.notifyWatchers();
398
+ }
399
+
400
+ /**
401
+ * Merge additional configuration
402
+ */
403
+ merge(config: DeepPartial<T>): void {
404
+ this.config = deepMerge(this.config, config) as T;
405
+ this.notifyWatchers();
406
+ }
407
+
408
+ /**
409
+ * Notify all watchers of a change
410
+ */
411
+ private notifyWatchers(): void {
412
+ for (const watcher of this.watchers) {
413
+ try {
414
+ watcher(this.config);
415
+ } catch (error) {
416
+ console.error("Error in config watcher:", error);
417
+ }
418
+ }
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Define configuration with type safety
424
+ * @example
425
+ * export default defineConfig({
426
+ * server: { port: 3000 },
427
+ * database: { url: process.env.DATABASE_URL },
428
+ * })
429
+ */
430
+ export function defineConfig<T extends BuenoConfig = BuenoConfig>(
431
+ config: UserConfig<T>,
432
+ ): UserConfig<T> {
433
+ return config;
434
+ }
435
+
436
+ /**
437
+ * Define configuration with a function
438
+ * @example
439
+ * export default defineConfigFn((env) => ({
440
+ * server: { port: env.PORT ? parseInt(env.PORT) : 3000 },
441
+ * }))
442
+ */
443
+ export function defineConfigFn<T extends BuenoConfig = BuenoConfig>(
444
+ fn: UserConfigFn<T>,
445
+ ): UserConfigFn<T> {
446
+ return fn;
447
+ }
448
+
449
+ /**
450
+ * Create a configuration manager
451
+ * @example
452
+ * const config = await createConfigManager();
453
+ * const port = config.get('server.port');
454
+ */
455
+ export async function createConfigManager<
456
+ T extends BuenoConfig = BuenoConfig,
457
+ >(options?: ConfigManagerOptions<T>): Promise<ConfigManager<T>> {
458
+ const manager = new ConfigManager<T>(options);
459
+ await manager.load();
460
+ return manager;
461
+ }
462
+
463
+ /**
464
+ * Create a configuration manager without loading
465
+ * @example
466
+ * const config = createConfigManagerSync();
467
+ * // Later: await config.load();
468
+ */
469
+ export function createConfigManagerSync<T extends BuenoConfig = BuenoConfig>(
470
+ options?: ConfigManagerOptions<T>,
471
+ ): ConfigManager<T> {
472
+ return new ConfigManager<T>(options);
473
+ }
474
+
475
+ /**
476
+ * Load configuration and return the config object directly
477
+ * @example
478
+ * const config = await loadConfigDirect();
479
+ * console.log(config.server?.port);
480
+ */
481
+ export async function loadConfigDirect<
482
+ T extends BuenoConfig = BuenoConfig,
483
+ >(options?: ConfigManagerOptions<T>): Promise<T> {
484
+ const manager = await createConfigManager<T>(options);
485
+ return manager.getAll();
486
+ }
487
+
488
+ // Singleton instance for convenience
489
+ let defaultManager: ConfigManager | undefined;
490
+
491
+ /**
492
+ * Get the default configuration manager
493
+ */
494
+ export function getConfigManager(): ConfigManager {
495
+ if (!defaultManager) {
496
+ defaultManager = new ConfigManager();
497
+ }
498
+ return defaultManager;
499
+ }
500
+
501
+ /**
502
+ * Set the default configuration manager
503
+ */
504
+ export function setConfigManager(manager: ConfigManager): void {
505
+ defaultManager = manager;
506
+ }