@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,371 @@
1
+ /**
2
+ * Database Migration System
3
+ *
4
+ * Provides utilities for managing database migrations with
5
+ * up/down support, version tracking, and rollback capabilities.
6
+ */
7
+
8
+ import type { Database } from "../index";
9
+ import {
10
+ type TableSchema,
11
+ generateCreateIndex,
12
+ generateCreateTable,
13
+ generateDropTable,
14
+ } from "../schema";
15
+
16
+ // ============= Types =============
17
+
18
+ export interface Migration {
19
+ id: string;
20
+ name: string;
21
+ up: (db: MigrationRunner) => Promise<void>;
22
+ down: (db: MigrationRunner) => Promise<void>;
23
+ }
24
+
25
+ export interface MigrationRecord {
26
+ id: string;
27
+ name: string;
28
+ executedAt: Date;
29
+ }
30
+
31
+ export interface MigrationOptions {
32
+ migrationsTable?: string;
33
+ migrationsDir?: string;
34
+ }
35
+
36
+ // ============= Migration Runner =============
37
+
38
+ export class MigrationRunner {
39
+ private db: Database;
40
+ private migrationsTable: string;
41
+
42
+ constructor(db: Database, options: MigrationOptions = {}) {
43
+ this.db = db;
44
+ this.migrationsTable = options.migrationsTable ?? "_migrations";
45
+ }
46
+
47
+ /**
48
+ * Ensure migrations table exists
49
+ */
50
+ async ensureMigrationsTable(): Promise<void> {
51
+ await this.db.raw(`
52
+ CREATE TABLE IF NOT EXISTS ${this.migrationsTable} (
53
+ id VARCHAR(255) PRIMARY KEY,
54
+ name VARCHAR(255) NOT NULL,
55
+ executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
56
+ )
57
+ `);
58
+ }
59
+
60
+ /**
61
+ * Get list of executed migrations
62
+ */
63
+ async getExecutedMigrations(): Promise<MigrationRecord[]> {
64
+ await this.ensureMigrationsTable();
65
+
66
+ const results = await this.db.raw<{
67
+ id: string;
68
+ name: string;
69
+ executed_at: string;
70
+ }>(
71
+ `SELECT id, name, executed_at FROM ${this.migrationsTable} ORDER BY id ASC`,
72
+ );
73
+
74
+ return results.map((r) => ({
75
+ id: r.id,
76
+ name: r.name,
77
+ executedAt: new Date(r.executed_at),
78
+ }));
79
+ }
80
+
81
+ /**
82
+ * Get pending migrations
83
+ */
84
+ async getPendingMigrations(migrations: Migration[]): Promise<Migration[]> {
85
+ const executed = await this.getExecutedMigrations();
86
+ const executedIds = new Set(executed.map((m) => m.id));
87
+
88
+ return migrations
89
+ .filter((m) => !executedIds.has(m.id))
90
+ .sort((a, b) => a.id.localeCompare(b.id));
91
+ }
92
+
93
+ /**
94
+ * Run a single migration
95
+ */
96
+ async runMigration(migration: Migration): Promise<void> {
97
+ await this.db.transaction(async () => {
98
+ await migration.up(this);
99
+
100
+ await this.db.raw(
101
+ `INSERT INTO ${this.migrationsTable} (id, name) VALUES (?, ?)`,
102
+ [migration.id, migration.name],
103
+ );
104
+ });
105
+ }
106
+
107
+ /**
108
+ * Run all pending migrations
109
+ */
110
+ async migrate(migrations: Migration[]): Promise<{ executed: string[] }> {
111
+ const pending = await this.getPendingMigrations(migrations);
112
+ const executed: string[] = [];
113
+
114
+ for (const migration of pending) {
115
+ await this.runMigration(migration);
116
+ executed.push(migration.id);
117
+ console.log(`Executed migration: ${migration.id} - ${migration.name}`);
118
+ }
119
+
120
+ return { executed };
121
+ }
122
+
123
+ /**
124
+ * Rollback last n migrations
125
+ */
126
+ async rollback(
127
+ migrations: Migration[],
128
+ steps = 1,
129
+ ): Promise<{ rolledBack: string[] }> {
130
+ const executed = await this.getExecutedMigrations();
131
+ const rolledBack: string[] = [];
132
+
133
+ // Sort migrations by id descending for rollback
134
+ const migrationsById = new Map(migrations.map((m) => [m.id, m]));
135
+
136
+ // Get migrations to rollback
137
+ const toRollback = executed
138
+ .sort((a, b) => b.id.localeCompare(a.id))
139
+ .slice(0, steps);
140
+
141
+ for (const record of toRollback) {
142
+ const migration = migrationsById.get(record.id);
143
+
144
+ if (!migration) {
145
+ console.warn(`Migration not found: ${record.id}`);
146
+ continue;
147
+ }
148
+
149
+ await this.db.transaction(async () => {
150
+ await migration.down(this);
151
+
152
+ await this.db.raw(`DELETE FROM ${this.migrationsTable} WHERE id = ?`, [
153
+ record.id,
154
+ ]);
155
+ });
156
+
157
+ rolledBack.push(record.id);
158
+ console.log(`Rolled back migration: ${record.id} - ${record.name}`);
159
+ }
160
+
161
+ return { rolledBack };
162
+ }
163
+
164
+ /**
165
+ * Reset all migrations
166
+ */
167
+ async reset(migrations: Migration[]): Promise<{ rolledBack: string[] }> {
168
+ const executed = await this.getExecutedMigrations();
169
+ return this.rollback(migrations, executed.length);
170
+ }
171
+
172
+ /**
173
+ * Refresh migrations (rollback all, then migrate all)
174
+ */
175
+ async refresh(
176
+ migrations: Migration[],
177
+ ): Promise<{ rolledBack: string[]; executed: string[] }> {
178
+ const { rolledBack } = await this.reset(migrations);
179
+ const { executed } = await this.migrate(migrations);
180
+ return { rolledBack, executed };
181
+ }
182
+
183
+ // ============= Schema Helpers =============
184
+
185
+ /**
186
+ * Create a table
187
+ */
188
+ async createTable(schema: TableSchema): Promise<void> {
189
+ const sql = generateCreateTable(schema, "postgresql");
190
+ await this.db.raw(sql);
191
+
192
+ // Create indexes
193
+ for (const index of schema.indexes ?? []) {
194
+ await this.createIndex(schema.name, index);
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Drop a table
200
+ */
201
+ async dropTable(name: string): Promise<void> {
202
+ const sql = generateDropTable(name);
203
+ await this.db.raw(sql);
204
+ }
205
+
206
+ /**
207
+ * Create an index
208
+ */
209
+ async createIndex(
210
+ tableName: string,
211
+ index: { name?: string; columns: string[]; unique?: boolean },
212
+ ): Promise<void> {
213
+ const sql = generateCreateIndex(tableName, index, "postgresql");
214
+ await this.db.raw(sql);
215
+ }
216
+
217
+ /**
218
+ * Drop an index
219
+ */
220
+ async dropIndex(name: string): Promise<void> {
221
+ await this.db.raw(`DROP INDEX IF EXISTS ${name}`);
222
+ }
223
+
224
+ /**
225
+ * Add a column
226
+ */
227
+ async addColumn(
228
+ table: string,
229
+ name: string,
230
+ type: string,
231
+ options?: {
232
+ nullable?: boolean;
233
+ default?: unknown;
234
+ },
235
+ ): Promise<void> {
236
+ let sql = `ALTER TABLE ${table} ADD COLUMN ${name} ${type}`;
237
+
238
+ if (options?.default !== undefined) {
239
+ sql += ` DEFAULT ${typeof options.default === "string" ? `'${options.default}'` : options.default}`;
240
+ }
241
+
242
+ if (!options?.nullable) {
243
+ sql += " NOT NULL";
244
+ }
245
+
246
+ await this.db.raw(sql);
247
+ }
248
+
249
+ /**
250
+ * Drop a column
251
+ */
252
+ async dropColumn(table: string, name: string): Promise<void> {
253
+ await this.db.raw(`ALTER TABLE ${table} DROP COLUMN ${name}`);
254
+ }
255
+
256
+ /**
257
+ * Rename a column
258
+ */
259
+ async renameColumn(
260
+ table: string,
261
+ oldName: string,
262
+ newName: string,
263
+ ): Promise<void> {
264
+ await this.db.raw(
265
+ `ALTER TABLE ${table} RENAME COLUMN ${oldName} TO ${newName}`,
266
+ );
267
+ }
268
+
269
+ /**
270
+ * Add a foreign key
271
+ */
272
+ async addForeignKey(
273
+ table: string,
274
+ columns: string[],
275
+ reference: { table: string; columns: string[] },
276
+ options?: {
277
+ name?: string;
278
+ onDelete?: "CASCADE" | "SET NULL" | "RESTRICT" | "NO ACTION";
279
+ onUpdate?: "CASCADE" | "SET NULL" | "RESTRICT" | "NO ACTION";
280
+ },
281
+ ): Promise<void> {
282
+ const name = options?.name ?? `fk_${table}_${columns.join("_")}`;
283
+ let sql = `ALTER TABLE ${table} ADD CONSTRAINT ${name} FOREIGN KEY (${columns.join(", ")}) REFERENCES ${reference.table}(${reference.columns.join(", ")})`;
284
+
285
+ if (options?.onDelete) sql += ` ON DELETE ${options.onDelete}`;
286
+ if (options?.onUpdate) sql += ` ON UPDATE ${options.onUpdate}`;
287
+
288
+ await this.db.raw(sql);
289
+ }
290
+
291
+ /**
292
+ * Drop a foreign key
293
+ */
294
+ async dropForeignKey(table: string, name: string): Promise<void> {
295
+ await this.db.raw(`ALTER TABLE ${table} DROP CONSTRAINT ${name}`);
296
+ }
297
+
298
+ /**
299
+ * Execute raw SQL
300
+ */
301
+ async raw(sql: string): Promise<void> {
302
+ await this.db.raw(sql);
303
+ }
304
+ }
305
+
306
+ // ============= Migration Builder =============
307
+
308
+ export class MigrationBuilder {
309
+ private id: string;
310
+ private name: string;
311
+ private upFn: (runner: MigrationRunner) => Promise<void> = async () => {};
312
+ private downFn: (runner: MigrationRunner) => Promise<void> = async () => {};
313
+
314
+ constructor(id: string, name: string) {
315
+ this.id = id;
316
+ this.name = name;
317
+ }
318
+
319
+ up(fn: (runner: MigrationRunner) => Promise<void>): this {
320
+ this.upFn = fn;
321
+ return this;
322
+ }
323
+
324
+ down(fn: (runner: MigrationRunner) => Promise<void>): this {
325
+ this.downFn = fn;
326
+ return this;
327
+ }
328
+
329
+ build(): Migration {
330
+ return {
331
+ id: this.id,
332
+ name: this.name,
333
+ up: this.upFn,
334
+ down: this.downFn,
335
+ };
336
+ }
337
+ }
338
+
339
+ // ============= Factory Functions =============
340
+
341
+ /**
342
+ * Create a migration builder
343
+ */
344
+ export function createMigration(id: string, name: string): MigrationBuilder {
345
+ return new MigrationBuilder(id, name);
346
+ }
347
+
348
+ /**
349
+ * Create a migration runner
350
+ */
351
+ export function createMigrationRunner(
352
+ db: Database,
353
+ options?: MigrationOptions,
354
+ ): MigrationRunner {
355
+ return new MigrationRunner(db, options);
356
+ }
357
+
358
+ /**
359
+ * Generate migration ID from timestamp
360
+ */
361
+ export function generateMigrationId(): string {
362
+ const now = new Date();
363
+ const year = now.getFullYear();
364
+ const month = String(now.getMonth() + 1).padStart(2, "0");
365
+ const day = String(now.getDate()).padStart(2, "0");
366
+ const hour = String(now.getHours()).padStart(2, "0");
367
+ const minute = String(now.getMinutes()).padStart(2, "0");
368
+ const second = String(now.getSeconds()).padStart(2, "0");
369
+
370
+ return `${year}${month}${day}${hour}${minute}${second}`;
371
+ }