@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,408 @@
1
+ /**
2
+ * Environment variable handling for Bueno Framework
3
+ */
4
+
5
+ import type { BuenoConfig, DeepPartial, EnvMapping } from "./types";
6
+ import { ENV_MAPPINGS } from "./types";
7
+ import { setNestedValue } from "./merge";
8
+
9
+ /**
10
+ * Environment variable source information
11
+ */
12
+ export interface EnvSourceInfo {
13
+ /** Environment variable name */
14
+ name: string;
15
+ /** Value of the environment variable */
16
+ value: string;
17
+ /** File the variable was loaded from */
18
+ source?: string;
19
+ }
20
+
21
+ /**
22
+ * Loaded environment data
23
+ */
24
+ export interface LoadedEnv {
25
+ /** Raw environment variables loaded */
26
+ raw: Record<string, string>;
27
+ /** Transformed configuration from environment */
28
+ config: DeepPartial<BuenoConfig>;
29
+ /** Source information for each variable */
30
+ sources: Map<string, EnvSourceInfo>;
31
+ }
32
+
33
+ /**
34
+ * Environment file priority (later files override earlier ones)
35
+ */
36
+ const ENV_FILE_PRIORITY = [
37
+ ".env",
38
+ ".env.local",
39
+ ".env.development",
40
+ ".env.production",
41
+ ".env.test",
42
+ ];
43
+
44
+ /**
45
+ * Get the current NODE_ENV
46
+ */
47
+ export function getNodeEnv(): string {
48
+ return Bun.env.NODE_ENV || "development";
49
+ }
50
+
51
+ /**
52
+ * Get environment-specific .env file name
53
+ */
54
+ export function getEnvFileName(): string {
55
+ const nodeEnv = getNodeEnv();
56
+ switch (nodeEnv) {
57
+ case "production":
58
+ return ".env.production";
59
+ case "test":
60
+ return ".env.test";
61
+ default:
62
+ return ".env.development";
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Check if a file exists
68
+ */
69
+ async function fileExists(path: string): Promise<boolean> {
70
+ try {
71
+ const file = Bun.file(path);
72
+ return await file.exists();
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Parse .env file content
80
+ */
81
+ function parseEnvContent(content: string): Record<string, string> {
82
+ const result: Record<string, string> = {};
83
+ const lines = content.split("\n");
84
+
85
+ for (const line of lines) {
86
+ // Skip empty lines and comments
87
+ const trimmed = line.trim();
88
+ if (!trimmed || trimmed.startsWith("#")) {
89
+ continue;
90
+ }
91
+
92
+ // Parse KEY=value or KEY="value" or KEY='value'
93
+ const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
94
+ if (!match) {
95
+ continue;
96
+ }
97
+
98
+ const [, key, rawValue] = match;
99
+ let value = rawValue;
100
+
101
+ // Handle quoted values
102
+ if (
103
+ (value.startsWith('"') && value.endsWith('"')) ||
104
+ (value.startsWith("'") && value.endsWith("'"))
105
+ ) {
106
+ value = value.slice(1, -1);
107
+ }
108
+
109
+ result[key] = value;
110
+ }
111
+
112
+ return result;
113
+ }
114
+
115
+ /**
116
+ * Load environment variables from a single .env file
117
+ */
118
+ async function loadEnvFile(
119
+ filePath: string,
120
+ ): Promise<{ vars: Record<string, string>; existed: boolean }> {
121
+ const exists = await fileExists(filePath);
122
+ if (!exists) {
123
+ return { vars: {}, existed: false };
124
+ }
125
+
126
+ try {
127
+ const file = Bun.file(filePath);
128
+ const content = await file.text();
129
+ const vars = parseEnvContent(content);
130
+ return { vars, existed: true };
131
+ } catch (error) {
132
+ console.warn(`Warning: Failed to load ${filePath}:`, error);
133
+ return { vars: {}, existed: false };
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Load environment variables from multiple .env files
139
+ * Files are loaded in priority order, with later files overriding earlier ones
140
+ */
141
+ export async function loadEnvFiles(options?: {
142
+ /** Custom list of env files to load */
143
+ files?: string[];
144
+ /** Whether to also load NODE_ENV-specific file */
145
+ loadNodeEnv?: boolean;
146
+ /** Base directory for env files */
147
+ cwd?: string;
148
+ }): Promise<Record<string, string>> {
149
+ const cwd = options?.cwd ?? process.cwd();
150
+ const files = options?.files ?? [...ENV_FILE_PRIORITY];
151
+
152
+ // Add NODE_ENV-specific file if requested
153
+ if (options?.loadNodeEnv !== false) {
154
+ const envFile = getEnvFileName();
155
+ if (!files.includes(envFile)) {
156
+ files.push(envFile);
157
+ }
158
+ }
159
+
160
+ const result: Record<string, string> = {};
161
+
162
+ for (const file of files) {
163
+ const filePath = file.startsWith("/") ? file : `${cwd}/${file}`;
164
+ const { vars } = await loadEnvFile(filePath);
165
+ Object.assign(result, vars);
166
+ }
167
+
168
+ return result;
169
+ }
170
+
171
+ /**
172
+ * Get environment variable value from Bun.env
173
+ */
174
+ export function getEnvVar(name: string): string | undefined {
175
+ return Bun.env[name];
176
+ }
177
+
178
+ /**
179
+ * Set environment variable in Bun.env
180
+ */
181
+ export function setEnvVar(name: string, value: string): void {
182
+ Bun.env[name] = value;
183
+ }
184
+
185
+ /**
186
+ * Delete environment variable from Bun.env
187
+ */
188
+ export function deleteEnvVar(name: string): void {
189
+ delete Bun.env[name];
190
+ }
191
+
192
+ /**
193
+ * Transform environment variables to configuration object
194
+ */
195
+ export function envToConfig(
196
+ envVars: Record<string, string>,
197
+ mappings: EnvMapping[] = ENV_MAPPINGS,
198
+ ): DeepPartial<BuenoConfig> {
199
+ let config: DeepPartial<BuenoConfig> = {};
200
+
201
+ for (const mapping of mappings) {
202
+ const value = envVars[mapping.envVar];
203
+ if (value === undefined || value === "") {
204
+ continue;
205
+ }
206
+
207
+ const transformedValue = mapping.transform
208
+ ? mapping.transform(value)
209
+ : value;
210
+
211
+ config = setNestedValue(
212
+ config as Record<string, unknown>,
213
+ mapping.configKey,
214
+ transformedValue,
215
+ ) as DeepPartial<BuenoConfig>;
216
+ }
217
+
218
+ return config;
219
+ }
220
+
221
+ /**
222
+ * Load environment variables and transform to configuration
223
+ */
224
+ export async function loadEnv(options?: {
225
+ /** Custom list of env files to load */
226
+ files?: string[];
227
+ /** Whether to load NODE_ENV-specific file */
228
+ loadNodeEnv?: boolean;
229
+ /** Base directory for env files */
230
+ cwd?: string;
231
+ /** Whether to merge with existing Bun.env */
232
+ mergeWithProcess?: boolean;
233
+ /** Custom environment variable mappings */
234
+ mappings?: EnvMapping[];
235
+ }): Promise<LoadedEnv> {
236
+ // Load from .env files
237
+ const fileVars = await loadEnvFiles(options);
238
+
239
+ // Merge with Bun.env if requested
240
+ const raw: Record<string, string> =
241
+ options?.mergeWithProcess !== false
242
+ ? { ...fileVars, ...Object.fromEntries(Object.entries(Bun.env).filter(([, v]) => v !== undefined) as [string, string][]) }
243
+ : fileVars;
244
+
245
+ // Transform to config
246
+ const config = envToConfig(raw, options?.mappings);
247
+
248
+ // Track sources
249
+ const sources = new Map<string, EnvSourceInfo>();
250
+ for (const [name, value] of Object.entries(raw)) {
251
+ sources.set(name, {
252
+ name,
253
+ value,
254
+ source: fileVars[name] !== undefined ? ".env file" : "process",
255
+ });
256
+ }
257
+
258
+ // Set loaded vars to Bun.env
259
+ for (const [key, value] of Object.entries(fileVars)) {
260
+ if (Bun.env[key] === undefined) {
261
+ Bun.env[key] = value;
262
+ }
263
+ }
264
+
265
+ return { raw, config, sources };
266
+ }
267
+
268
+ /**
269
+ * Get all environment variables related to Bueno configuration
270
+ */
271
+ export function getBuenoEnvVars(): Record<string, string> {
272
+ const result: Record<string, string> = {};
273
+
274
+ for (const mapping of ENV_MAPPINGS) {
275
+ const value = Bun.env[mapping.envVar];
276
+ if (value !== undefined) {
277
+ result[mapping.envVar] = value;
278
+ }
279
+ }
280
+
281
+ return result;
282
+ }
283
+
284
+ /**
285
+ * Check if running in development mode
286
+ */
287
+ export function isDevelopment(): boolean {
288
+ return getNodeEnv() === "development";
289
+ }
290
+
291
+ /**
292
+ * Check if running in production mode
293
+ */
294
+ export function isProduction(): boolean {
295
+ return getNodeEnv() === "production";
296
+ }
297
+
298
+ /**
299
+ * Check if running in test mode
300
+ */
301
+ export function isTest(): boolean {
302
+ return getNodeEnv() === "test";
303
+ }
304
+
305
+ /**
306
+ * Create a custom environment variable mapping
307
+ */
308
+ export function createEnvMapping(
309
+ envVar: string,
310
+ configKey: string,
311
+ transform?: (value: string) => unknown,
312
+ ): EnvMapping {
313
+ return { envVar, configKey, transform };
314
+ }
315
+
316
+ /**
317
+ * Parse a boolean environment variable
318
+ */
319
+ export function parseEnvBoolean(value: string): boolean {
320
+ return value === "true" || value === "1" || value === "yes";
321
+ }
322
+
323
+ /**
324
+ * Parse a number environment variable
325
+ */
326
+ export function parseEnvNumber(value: string): number {
327
+ const num = parseInt(value, 10);
328
+ if (isNaN(num)) {
329
+ throw new Error(`Invalid number: ${value}`);
330
+ }
331
+ return num;
332
+ }
333
+
334
+ /**
335
+ * Parse a JSON environment variable
336
+ */
337
+ export function parseEnvJSON<T = unknown>(value: string): T {
338
+ return JSON.parse(value) as T;
339
+ }
340
+
341
+ /**
342
+ * Parse an array environment variable (comma-separated)
343
+ */
344
+ export function parseEnvArray(value: string): string[] {
345
+ return value.split(",").map((v) => v.trim());
346
+ }
347
+
348
+ /**
349
+ * Environment config mapping for standard Bueno config
350
+ */
351
+ export const envConfigMapping: EnvConfigMapping[] = [
352
+ { envVar: "BUENO_PORT", configKey: "server.port", transform: parseEnvNumber },
353
+ { envVar: "BUENO_HOST", configKey: "server.host" },
354
+ { envVar: "BUENO_DEV", configKey: "server.development", transform: parseEnvBoolean },
355
+ { envVar: "DATABASE_URL", configKey: "database.url" },
356
+ { envVar: "DATABASE_POOL_SIZE", configKey: "database.poolSize", transform: parseEnvNumber },
357
+ { envVar: "REDIS_URL", configKey: "cache.url" },
358
+ { envVar: "CACHE_DRIVER", configKey: "cache.driver" },
359
+ { envVar: "CACHE_TTL", configKey: "cache.ttl", transform: parseEnvNumber },
360
+ { envVar: "LOG_LEVEL", configKey: "logger.level" },
361
+ { envVar: "LOG_PRETTY", configKey: "logger.pretty", transform: parseEnvBoolean },
362
+ { envVar: "HEALTH_ENABLED", configKey: "health.enabled", transform: parseEnvBoolean },
363
+ { envVar: "METRICS_ENABLED", configKey: "metrics.enabled", transform: parseEnvBoolean },
364
+ { envVar: "TELEMETRY_ENABLED", configKey: "telemetry.enabled", transform: parseEnvBoolean },
365
+ { envVar: "TELEMETRY_SERVICE_NAME", configKey: "telemetry.serviceName" },
366
+ { envVar: "TELEMETRY_ENDPOINT", configKey: "telemetry.endpoint" },
367
+ { envVar: "FRONTEND_DEV_SERVER", configKey: "frontend.devServer", transform: parseEnvBoolean },
368
+ { envVar: "FRONTEND_HMR", configKey: "frontend.hmr", transform: parseEnvBoolean },
369
+ { envVar: "FRONTEND_PORT", configKey: "frontend.port", transform: parseEnvNumber },
370
+ ];
371
+
372
+ /**
373
+ * Environment config mapping interface
374
+ */
375
+ export interface EnvConfigMapping {
376
+ envVar: string;
377
+ configKey: string;
378
+ transform?: (value: string) => unknown;
379
+ }
380
+
381
+ /**
382
+ * Get a config value from environment variables using the mapping
383
+ */
384
+ export function getEnvConfig(
385
+ mappings: EnvConfigMapping[] = envConfigMapping,
386
+ ): DeepPartial<BuenoConfig> {
387
+ const envVars: Record<string, string> = {};
388
+ for (const [key, value] of Object.entries(Bun.env)) {
389
+ if (value !== undefined) {
390
+ envVars[key] = value;
391
+ }
392
+ }
393
+ return envToConfig(envVars, mappings);
394
+ }
395
+
396
+ /**
397
+ * Get an environment variable value
398
+ */
399
+ export function getEnvValue(key: string, defaultValue?: string): string | undefined {
400
+ return Bun.env[key] ?? defaultValue;
401
+ }
402
+
403
+ /**
404
+ * Set an environment variable value
405
+ */
406
+ export function setEnvValue(key: string, value: string): void {
407
+ Bun.env[key] = value;
408
+ }