@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.
- package/.env.example +109 -0
- package/.github/workflows/ci.yml +31 -0
- package/LICENSE +21 -0
- package/README.md +892 -0
- package/architecture.md +652 -0
- package/bun.lock +70 -0
- package/dist/cli/index.js +3233 -0
- package/dist/index.js +9014 -0
- package/package.json +77 -0
- package/src/cache/index.ts +795 -0
- package/src/cli/ARCHITECTURE.md +837 -0
- package/src/cli/bin.ts +10 -0
- package/src/cli/commands/build.ts +425 -0
- package/src/cli/commands/dev.ts +248 -0
- package/src/cli/commands/generate.ts +541 -0
- package/src/cli/commands/help.ts +55 -0
- package/src/cli/commands/index.ts +112 -0
- package/src/cli/commands/migration.ts +355 -0
- package/src/cli/commands/new.ts +804 -0
- package/src/cli/commands/start.ts +208 -0
- package/src/cli/core/args.ts +283 -0
- package/src/cli/core/console.ts +349 -0
- package/src/cli/core/index.ts +60 -0
- package/src/cli/core/prompt.ts +424 -0
- package/src/cli/core/spinner.ts +265 -0
- package/src/cli/index.ts +135 -0
- package/src/cli/templates/deploy.ts +295 -0
- package/src/cli/templates/docker.ts +307 -0
- package/src/cli/templates/index.ts +24 -0
- package/src/cli/utils/fs.ts +428 -0
- package/src/cli/utils/index.ts +8 -0
- package/src/cli/utils/strings.ts +197 -0
- package/src/config/env.ts +408 -0
- package/src/config/index.ts +506 -0
- package/src/config/loader.ts +329 -0
- package/src/config/merge.ts +285 -0
- package/src/config/types.ts +320 -0
- package/src/config/validation.ts +441 -0
- package/src/container/forward-ref.ts +143 -0
- package/src/container/index.ts +386 -0
- package/src/context/index.ts +360 -0
- package/src/database/index.ts +1142 -0
- package/src/database/migrations/index.ts +371 -0
- package/src/database/schema/index.ts +619 -0
- package/src/frontend/api-routes.ts +640 -0
- package/src/frontend/bundler.ts +643 -0
- package/src/frontend/console-client.ts +419 -0
- package/src/frontend/console-stream.ts +587 -0
- package/src/frontend/dev-server.ts +846 -0
- package/src/frontend/file-router.ts +611 -0
- package/src/frontend/frameworks/index.ts +106 -0
- package/src/frontend/frameworks/react.ts +85 -0
- package/src/frontend/frameworks/solid.ts +104 -0
- package/src/frontend/frameworks/svelte.ts +110 -0
- package/src/frontend/frameworks/vue.ts +92 -0
- package/src/frontend/hmr-client.ts +663 -0
- package/src/frontend/hmr.ts +728 -0
- package/src/frontend/index.ts +342 -0
- package/src/frontend/islands.ts +552 -0
- package/src/frontend/isr.ts +555 -0
- package/src/frontend/layout.ts +475 -0
- package/src/frontend/ssr/react.ts +446 -0
- package/src/frontend/ssr/solid.ts +523 -0
- package/src/frontend/ssr/svelte.ts +546 -0
- package/src/frontend/ssr/vue.ts +504 -0
- package/src/frontend/ssr.ts +699 -0
- package/src/frontend/types.ts +2274 -0
- package/src/health/index.ts +604 -0
- package/src/index.ts +410 -0
- package/src/lock/index.ts +587 -0
- package/src/logger/index.ts +444 -0
- package/src/logger/transports/index.ts +969 -0
- package/src/metrics/index.ts +494 -0
- package/src/middleware/built-in.ts +360 -0
- package/src/middleware/index.ts +94 -0
- package/src/modules/filters.ts +458 -0
- package/src/modules/guards.ts +405 -0
- package/src/modules/index.ts +1256 -0
- package/src/modules/interceptors.ts +574 -0
- package/src/modules/lazy.ts +418 -0
- package/src/modules/lifecycle.ts +478 -0
- package/src/modules/metadata.ts +90 -0
- package/src/modules/pipes.ts +626 -0
- package/src/router/index.ts +339 -0
- package/src/router/linear.ts +371 -0
- package/src/router/regex.ts +292 -0
- package/src/router/tree.ts +562 -0
- package/src/rpc/index.ts +1263 -0
- package/src/security/index.ts +436 -0
- package/src/ssg/index.ts +631 -0
- package/src/storage/index.ts +456 -0
- package/src/telemetry/index.ts +1097 -0
- package/src/testing/index.ts +1586 -0
- package/src/types/index.ts +236 -0
- package/src/types/optional-deps.d.ts +219 -0
- package/src/validation/index.ts +276 -0
- package/src/websocket/index.ts +1004 -0
- package/tests/integration/cli.test.ts +1016 -0
- package/tests/integration/fullstack.test.ts +234 -0
- package/tests/unit/cache.test.ts +174 -0
- package/tests/unit/cli-commands.test.ts +892 -0
- package/tests/unit/cli.test.ts +1258 -0
- package/tests/unit/container.test.ts +279 -0
- package/tests/unit/context.test.ts +221 -0
- package/tests/unit/database.test.ts +183 -0
- package/tests/unit/linear-router.test.ts +280 -0
- package/tests/unit/lock.test.ts +336 -0
- package/tests/unit/middleware.test.ts +184 -0
- package/tests/unit/modules.test.ts +142 -0
- package/tests/unit/pubsub.test.ts +257 -0
- package/tests/unit/regex-router.test.ts +265 -0
- package/tests/unit/router.test.ts +373 -0
- package/tests/unit/rpc.test.ts +1248 -0
- package/tests/unit/security.test.ts +174 -0
- package/tests/unit/telemetry.test.ts +371 -0
- package/tests/unit/test-cache.test.ts +110 -0
- package/tests/unit/test-database.test.ts +282 -0
- package/tests/unit/tree-router.test.ts +325 -0
- package/tests/unit/validation.test.ts +794 -0
- 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
|
+
}
|