@bungres/kit 0.1.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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +2435 -0
  6. package/dist/commands/drop.d.ts +5 -0
  7. package/dist/commands/drop.d.ts.map +1 -0
  8. package/dist/commands/fresh.d.ts +3 -0
  9. package/dist/commands/fresh.d.ts.map +1 -0
  10. package/dist/commands/generate.d.ts +3 -0
  11. package/dist/commands/generate.d.ts.map +1 -0
  12. package/dist/commands/migrate.d.ts +3 -0
  13. package/dist/commands/migrate.d.ts.map +1 -0
  14. package/dist/commands/pull.d.ts +29 -0
  15. package/dist/commands/pull.d.ts.map +1 -0
  16. package/dist/commands/push.d.ts +5 -0
  17. package/dist/commands/push.d.ts.map +1 -0
  18. package/dist/commands/refresh.d.ts +3 -0
  19. package/dist/commands/refresh.d.ts.map +1 -0
  20. package/dist/commands/seed.d.ts +3 -0
  21. package/dist/commands/seed.d.ts.map +1 -0
  22. package/dist/commands/status.d.ts +3 -0
  23. package/dist/commands/status.d.ts.map +1 -0
  24. package/dist/commands/studio.d.ts +3 -0
  25. package/dist/commands/studio.d.ts.map +1 -0
  26. package/dist/commands/tusky.d.ts +3 -0
  27. package/dist/commands/tusky.d.ts.map +1 -0
  28. package/dist/config.d.ts +44 -0
  29. package/dist/config.d.ts.map +1 -0
  30. package/dist/differ.d.ts +11 -0
  31. package/dist/differ.d.ts.map +1 -0
  32. package/dist/ensure-db.d.ts +2 -0
  33. package/dist/ensure-db.d.ts.map +1 -0
  34. package/dist/index.d.ts +13 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +1720 -0
  37. package/dist/schema-loader.d.ts +9 -0
  38. package/dist/schema-loader.d.ts.map +1 -0
  39. package/dist/utils/colors.d.ts +2 -0
  40. package/dist/utils/colors.d.ts.map +1 -0
  41. package/package.json +43 -0
  42. package/src/cli.ts +162 -0
  43. package/src/commands/drop.ts +92 -0
  44. package/src/commands/fresh.ts +17 -0
  45. package/src/commands/generate.ts +151 -0
  46. package/src/commands/migrate.ts +84 -0
  47. package/src/commands/pull.ts +339 -0
  48. package/src/commands/push.ts +105 -0
  49. package/src/commands/refresh.ts +37 -0
  50. package/src/commands/seed.ts +41 -0
  51. package/src/commands/status.ts +64 -0
  52. package/src/commands/studio.ts +471 -0
  53. package/src/commands/tusky.ts +83 -0
  54. package/src/config.ts +102 -0
  55. package/src/differ.ts +236 -0
  56. package/src/ensure-db.ts +32 -0
  57. package/src/index.ts +21 -0
  58. package/src/schema-loader.ts +50 -0
  59. package/src/utils/colors.ts +4 -0
package/dist/cli.js ADDED
@@ -0,0 +1,2435 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // src/config.ts
5
+ import { resolve, join } from "path";
6
+ var CONFIG_FILES = [
7
+ "bungres.config.ts",
8
+ "bungres.config.js",
9
+ "bungres.config.mts",
10
+ "bungres.config.mjs"
11
+ ];
12
+ async function loadConfig(cwd = process.cwd()) {
13
+ let userConfig = {};
14
+ for (const file of CONFIG_FILES) {
15
+ const configPath = join(cwd, file);
16
+ if (await Bun.file(configPath).exists()) {
17
+ const mod = await import(resolve(configPath));
18
+ userConfig = mod.default ?? mod;
19
+ break;
20
+ }
21
+ }
22
+ const dbUrl = userConfig.dbCredentials?.url ?? process.env["DATABASE_URL"] ?? process.env["POSTGRES_URL"] ?? "";
23
+ if (!dbUrl) {
24
+ console.error(`bungres: No database URL found.
25
+ Add dbCredentials.url to bungres.config.ts or set DATABASE_URL.`);
26
+ process.exit(1);
27
+ }
28
+ const out = userConfig.out ?? "./migrations";
29
+ return {
30
+ schema: userConfig.schema ?? "src/db/schema/**/*.ts",
31
+ seed: userConfig.seed ?? "src/db/seed.ts",
32
+ out,
33
+ dbUrl,
34
+ dbSchema: userConfig.dbSchema ?? "public",
35
+ migrationsDir: out,
36
+ outDir: "./src/db/generated",
37
+ verbose: userConfig.verbose ?? false
38
+ };
39
+ }
40
+
41
+ // src/commands/push.ts
42
+ import * as fs from "fs";
43
+
44
+ // ../bungres-orm/src/table.ts
45
+ var TableConfigSymbol = Symbol.for("BungresTableConfig");
46
+ function getTableConfig(table) {
47
+ return table[TableConfigSymbol];
48
+ }
49
+ function camelToSnakeCase(str) {
50
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
51
+ }
52
+ function createTableFactory(casing) {
53
+ return function(name, columns, extra) {
54
+ const columnConfigs = Object.fromEntries(Object.entries(columns).map(([key, config2]) => {
55
+ const c = { ...config2 };
56
+ if (!c.name) {
57
+ if (casing === "snake") {
58
+ c.name = camelToSnakeCase(key);
59
+ } else {
60
+ c.name = key;
61
+ }
62
+ }
63
+ return [key, c];
64
+ }));
65
+ const schema = extra?.schema;
66
+ const qualifiedName = schema ? `"${schema}"."${name}"` : `"${name}"`;
67
+ const tableObj = {
68
+ [TableConfigSymbol]: {
69
+ name,
70
+ schema,
71
+ columns: columnConfigs,
72
+ indexes: extra?.indexes ?? [],
73
+ checks: extra?.checks ?? [],
74
+ primaryKeys: extra?.primaryKeys ?? [],
75
+ qualifiedName
76
+ }
77
+ };
78
+ return Object.assign(tableObj, columnConfigs);
79
+ };
80
+ }
81
+ var table = createTableFactory("none");
82
+ var snakeCase = { table: createTableFactory("snake") };
83
+ var camelCase = { table: createTableFactory("camel") };
84
+ // ../bungres-orm/src/column.ts
85
+ function buildColumn(dataType, nameOrOpts, opts) {
86
+ let name = "";
87
+ let options = opts;
88
+ if (typeof nameOrOpts === "string") {
89
+ name = nameOrOpts;
90
+ } else if (nameOrOpts !== undefined) {
91
+ options = nameOrOpts;
92
+ }
93
+ const isPrimary = options?.primaryKey ?? false;
94
+ const isNotNull = isPrimary || (options?.notNull ?? false);
95
+ let defaultFn = options?.defaultRaw;
96
+ if (isPrimary && dataType === "uuid" && !defaultFn) {
97
+ defaultFn = "gen_random_uuid()";
98
+ }
99
+ const config2 = {
100
+ name,
101
+ dataType,
102
+ notNull: isNotNull,
103
+ primaryKey: isPrimary,
104
+ unique: options?.unique ?? false,
105
+ defaultValue: options?.default,
106
+ ...defaultFn !== undefined ? { defaultFn } : {},
107
+ ...options?.references !== undefined ? { references: options.references } : {},
108
+ ...options?.check !== undefined ? { check: options.check } : {}
109
+ };
110
+ return Object.assign(config2, {
111
+ as(alias) {
112
+ return Object.assign({}, this, { alias });
113
+ }
114
+ });
115
+ }
116
+ var col = (dataType) => (nameOrOpts, opts) => buildColumn(dataType, nameOrOpts, opts);
117
+ var integer = col("integer");
118
+ var bigint = col("bigint");
119
+ var smallint = col("smallint");
120
+ var boolean = col("boolean");
121
+ var real = col("real");
122
+ var doublePrecision = col("double precision");
123
+ var numeric = col("numeric");
124
+ var decimal = col("decimal");
125
+ var json = col("json");
126
+ var jsonb = col("jsonb");
127
+ var timestamp = col("timestamp");
128
+ var timestamptz = col("timestamptz");
129
+ var date = col("date");
130
+ var time = col("time");
131
+ var timetz = col("timetz");
132
+ var uuid = col("uuid");
133
+ var bytea = col("bytea");
134
+ var interval = col("interval");
135
+ var inet = col("inet");
136
+ var cidr = col("cidr");
137
+ var macaddr = col("macaddr");
138
+ // ../bungres-orm/src/sql.ts
139
+ function sqlJoin(chunks, separator = ", ") {
140
+ const params = [];
141
+ const parts = [];
142
+ for (const chunk of chunks) {
143
+ const offset = params.length;
144
+ parts.push(chunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`));
145
+ params.push(...chunk.params);
146
+ }
147
+ return { sql: parts.join(separator), params };
148
+ }
149
+ // ../bungres-orm/src/query.ts
150
+ class SelectBuilder {
151
+ _table;
152
+ _executor;
153
+ _where = [];
154
+ _orderBy = [];
155
+ _limit;
156
+ _offset;
157
+ _select;
158
+ _selection;
159
+ _joins = [];
160
+ _comment;
161
+ constructor(table2, executor, selection) {
162
+ this._table = table2;
163
+ this._executor = executor;
164
+ this._selection = selection;
165
+ }
166
+ then(onfulfilled, onrejected) {
167
+ return this._executor.execute(this).then(onfulfilled, onrejected);
168
+ }
169
+ select(...columns) {
170
+ this._select = columns;
171
+ return this;
172
+ }
173
+ comment(tag) {
174
+ this._comment = tag;
175
+ return this;
176
+ }
177
+ where(condition) {
178
+ this._where.push(condition);
179
+ return this;
180
+ }
181
+ orderBy(column, dir = "asc") {
182
+ this._orderBy.push({ column: typeof column === "string" ? column : column.name, dir });
183
+ return this;
184
+ }
185
+ limit(n) {
186
+ this._limit = n;
187
+ return this;
188
+ }
189
+ offset(n) {
190
+ this._offset = n;
191
+ return this;
192
+ }
193
+ join(rawClause) {
194
+ this._joins.push(rawClause);
195
+ return this;
196
+ }
197
+ toSQL() {
198
+ let cols = "";
199
+ if (this._selection) {
200
+ cols = Object.entries(this._selection).map(([alias, col2]) => `"${col2.name}" AS "${alias}"`).join(", ");
201
+ } else if (this._select && this._select.length > 0) {
202
+ cols = this._select.map((c) => {
203
+ if (typeof c === "string") {
204
+ return `"${getTableConfig(this._table).columns[c]?.name ?? c}" AS "${c}"`;
205
+ }
206
+ return `"${c.name}" AS "${c.alias || c.name}"`;
207
+ }).join(", ");
208
+ } else {
209
+ cols = Object.keys(getTableConfig(this._table).columns).map((c) => `"${getTableConfig(this._table).columns[c].name}" AS "${c}"`).join(", ");
210
+ }
211
+ let query = `SELECT ${cols} FROM ${getTableConfig(this._table).qualifiedName}`;
212
+ if (this._joins.length > 0) {
213
+ query += " " + this._joins.join(" ");
214
+ }
215
+ const params = [];
216
+ if (this._where.length > 0) {
217
+ const combined = sqlJoin(this._where, " AND ");
218
+ const offset = 0;
219
+ query += " WHERE " + combined.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
220
+ params.push(...combined.params);
221
+ }
222
+ if (this._orderBy.length > 0) {
223
+ query += " ORDER BY " + this._orderBy.map((o) => {
224
+ const dbCol = getTableConfig(this._table).columns[o.column]?.name ?? o.column;
225
+ return `"${dbCol}" ${o.dir.toUpperCase()}`;
226
+ }).join(", ");
227
+ }
228
+ if (this._limit !== undefined) {
229
+ params.push(this._limit);
230
+ query += ` LIMIT $${params.length}`;
231
+ }
232
+ if (this._offset !== undefined) {
233
+ params.push(this._offset);
234
+ query += ` OFFSET $${params.length}`;
235
+ }
236
+ if (this._comment) {
237
+ query += ` /* ${this._comment} */`;
238
+ }
239
+ return { sql: query, params };
240
+ }
241
+ }
242
+
243
+ class SelectBuilderIntermediate {
244
+ _executor;
245
+ _selection;
246
+ constructor(_executor, _selection) {
247
+ this._executor = _executor;
248
+ this._selection = _selection;
249
+ }
250
+ from(table2) {
251
+ return new SelectBuilder(table2, this._executor, this._selection);
252
+ }
253
+ }
254
+
255
+ class InsertBuilder {
256
+ _table;
257
+ _executor;
258
+ _values = [];
259
+ _onConflict;
260
+ _returning;
261
+ _comment;
262
+ constructor(table2, executor) {
263
+ this._table = table2;
264
+ this._executor = executor;
265
+ }
266
+ then(onfulfilled, onrejected) {
267
+ return this._executor.execute(this).then(onfulfilled, onrejected);
268
+ }
269
+ values(data) {
270
+ const rows = Array.isArray(data) ? data : [data];
271
+ this._values.push(...rows);
272
+ return this;
273
+ }
274
+ onConflictDoNothing() {
275
+ this._onConflict = "do nothing";
276
+ return this;
277
+ }
278
+ onConflictDoUpdate(clause) {
279
+ this._onConflict = clause;
280
+ return this;
281
+ }
282
+ returning(...columns) {
283
+ this._returning = columns.length > 0 ? columns : ["*"];
284
+ return this;
285
+ }
286
+ comment(tag) {
287
+ this._comment = tag;
288
+ return this;
289
+ }
290
+ toSQL() {
291
+ if (this._values.length === 0) {
292
+ throw new Error("InsertBuilder: no values provided");
293
+ }
294
+ const keys = Array.from(new Set(this._values.flatMap((v) => Object.keys(v))));
295
+ const params = [];
296
+ const rowPlaceholders = [];
297
+ for (const row of this._values) {
298
+ const placeholders = [];
299
+ for (const key of keys) {
300
+ params.push(row[key] ?? null);
301
+ placeholders.push(`$${params.length}`);
302
+ }
303
+ rowPlaceholders.push(`(${placeholders.join(", ")})`);
304
+ }
305
+ const colList = keys.map((k) => `"${getTableConfig(this._table).columns[k]?.name ?? k}"`).join(", ");
306
+ let query = `INSERT INTO ${getTableConfig(this._table).qualifiedName} (${colList}) VALUES ${rowPlaceholders.join(", ")}`;
307
+ if (this._onConflict === "do nothing") {
308
+ query += " ON CONFLICT DO NOTHING";
309
+ } else if (this._onConflict && typeof this._onConflict === "object") {
310
+ const offset = params.length;
311
+ query += " ON CONFLICT " + this._onConflict.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
312
+ params.push(...this._onConflict.params);
313
+ }
314
+ if (this._returning) {
315
+ query += " RETURNING " + (this._returning[0] === "*" ? Object.keys(getTableConfig(this._table).columns).map((c) => `"${getTableConfig(this._table).columns[c].name}" AS "${c}"`).join(", ") : this._returning.map((c) => `"${getTableConfig(this._table).columns[c]?.name ?? c}" AS "${c}"`).join(", "));
316
+ }
317
+ return { sql: query, params };
318
+ }
319
+ }
320
+
321
+ class UpdateBuilder {
322
+ _table;
323
+ _executor;
324
+ _set = {};
325
+ _where = [];
326
+ _returning;
327
+ _comment;
328
+ constructor(table2, executor) {
329
+ this._table = table2;
330
+ this._executor = executor;
331
+ }
332
+ then(onfulfilled, onrejected) {
333
+ return this._executor.execute(this).then(onfulfilled, onrejected);
334
+ }
335
+ set(data) {
336
+ this._set = { ...this._set, ...data };
337
+ return this;
338
+ }
339
+ where(condition) {
340
+ this._where.push(condition);
341
+ return this;
342
+ }
343
+ returning(...columns) {
344
+ this._returning = columns.length > 0 ? columns : ["*"];
345
+ return this;
346
+ }
347
+ comment(tag) {
348
+ this._comment = tag;
349
+ return this;
350
+ }
351
+ toSQL() {
352
+ const entries = Object.entries(this._set);
353
+ if (entries.length === 0) {
354
+ throw new Error("UpdateBuilder: no fields to set");
355
+ }
356
+ const params = [];
357
+ const setClauses = entries.map(([key, value]) => {
358
+ params.push(value);
359
+ const dbCol = getTableConfig(this._table).columns[key]?.name ?? key;
360
+ return `"${dbCol}" = $${params.length}`;
361
+ });
362
+ let query = `UPDATE ${getTableConfig(this._table).qualifiedName} SET ${setClauses.join(", ")}`;
363
+ if (this._where.length > 0) {
364
+ const combined = sqlJoin(this._where, " AND ");
365
+ const offset = params.length;
366
+ query += " WHERE " + combined.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
367
+ params.push(...combined.params);
368
+ }
369
+ if (this._returning) {
370
+ query += " RETURNING " + (this._returning[0] === "*" ? Object.keys(getTableConfig(this._table).columns).map((c) => `"${getTableConfig(this._table).columns[c].name}" AS "${c}"`).join(", ") : this._returning.map((c) => `"${getTableConfig(this._table).columns[c]?.name ?? c}" AS "${c}"`).join(", "));
371
+ }
372
+ return { sql: query, params };
373
+ }
374
+ }
375
+
376
+ class DeleteBuilder {
377
+ _table;
378
+ _executor;
379
+ _where = [];
380
+ _returning;
381
+ _comment;
382
+ constructor(table2, executor) {
383
+ this._table = table2;
384
+ this._executor = executor;
385
+ }
386
+ then(onfulfilled, onrejected) {
387
+ return this._executor.execute(this).then(onfulfilled, onrejected);
388
+ }
389
+ where(condition) {
390
+ this._where.push(condition);
391
+ return this;
392
+ }
393
+ returning(...columns) {
394
+ this._returning = columns.length > 0 ? columns : ["*"];
395
+ return this;
396
+ }
397
+ comment(tag) {
398
+ this._comment = tag;
399
+ return this;
400
+ }
401
+ toSQL() {
402
+ let query = `DELETE FROM ${getTableConfig(this._table).qualifiedName}`;
403
+ const params = [];
404
+ if (this._where.length > 0) {
405
+ const combined = sqlJoin(this._where, " AND ");
406
+ query += " WHERE " + combined.sql;
407
+ params.push(...combined.params);
408
+ }
409
+ if (this._returning) {
410
+ query += " RETURNING " + (this._returning[0] === "*" ? Object.keys(getTableConfig(this._table).columns).map((c) => `"${getTableConfig(this._table).columns[c].name}" AS "${c}"`).join(", ") : this._returning.map((c) => `"${getTableConfig(this._table).columns[c]?.name ?? c}" AS "${c}"`).join(", "));
411
+ }
412
+ return { sql: query, params };
413
+ }
414
+ }
415
+ // ../bungres-orm/src/relational.ts
416
+ var _relationsCache = new WeakMap;
417
+
418
+ class RelationalQueryBuilder {
419
+ _executor;
420
+ _schema;
421
+ _tableName;
422
+ _args;
423
+ constructor(_executor, _schema, _tableName, args) {
424
+ this._executor = _executor;
425
+ this._schema = _schema;
426
+ this._tableName = _tableName;
427
+ this._args = args ?? {};
428
+ }
429
+ where(condition) {
430
+ return new RelationalQueryBuilder(this._executor, this._schema, this._tableName, {
431
+ ...this._args,
432
+ where: condition
433
+ });
434
+ }
435
+ limit(n) {
436
+ return new RelationalQueryBuilder(this._executor, this._schema, this._tableName, {
437
+ ...this._args,
438
+ limit: n
439
+ });
440
+ }
441
+ offset(n) {
442
+ return new RelationalQueryBuilder(this._executor, this._schema, this._tableName, {
443
+ ...this._args,
444
+ offset: n
445
+ });
446
+ }
447
+ orderBy(order) {
448
+ return new RelationalQueryBuilder(this._executor, this._schema, this._tableName, {
449
+ ...this._args,
450
+ orderBy: order
451
+ });
452
+ }
453
+ select(...fields) {
454
+ const columnsConfig = Object.fromEntries(fields.map((f) => [f, true]));
455
+ return new RelationalQueryBuilder(this._executor, this._schema, this._tableName, {
456
+ ...this._args,
457
+ columns: {
458
+ ...this._args.columns || {},
459
+ ...columnsConfig
460
+ }
461
+ });
462
+ }
463
+ with(relation, callback) {
464
+ const relations = this._getRuntimeRelations(this._tableName);
465
+ const rel = relations.ones[relation] || relations.manys[relation] || relations.manyToManys[relation];
466
+ if (!rel)
467
+ throw new Error(`Relation ${String(relation)} not found on table ${String(this._tableName)}`);
468
+ const subQb = new RelationalQueryBuilder(this._executor, this._schema, rel.targetTable);
469
+ const configuredSubQb = callback ? callback(subQb) : subQb;
470
+ const newArgs = { ...this._args };
471
+ newArgs.with = { ...newArgs.with || {}, [relation]: callback ? configuredSubQb._args : true };
472
+ return new RelationalQueryBuilder(this._executor, this._schema, this._tableName, newArgs);
473
+ }
474
+ async findMany(args) {
475
+ const finalArgs = { ...this._args, ...args ?? {} };
476
+ const chunk = this.buildSQL(finalArgs);
477
+ const rows = await this._executor.execute(chunk);
478
+ return rows.map((r) => typeof r._data === "string" ? JSON.parse(r._data) : r._data);
479
+ }
480
+ async findFirst(args) {
481
+ const finalArgs = { ...this._args, ...args ?? {}, limit: 1 };
482
+ const res = await this.findMany(finalArgs);
483
+ return res[0] ?? null;
484
+ }
485
+ _getRuntimeRelations(tableName) {
486
+ let schemaCache = _relationsCache.get(this._schema);
487
+ if (!schemaCache) {
488
+ schemaCache = new Map;
489
+ _relationsCache.set(this._schema, schemaCache);
490
+ }
491
+ let cached = schemaCache.get(tableName);
492
+ if (cached)
493
+ return cached;
494
+ const table2 = this._schema[tableName];
495
+ const tConfig = table2[TableConfigSymbol];
496
+ const ones = {};
497
+ const manys = {};
498
+ const manyToManys = {};
499
+ for (const [colName, col2] of Object.entries(tConfig.columns)) {
500
+ if (col2.references) {
501
+ const ref = col2.references;
502
+ const relName = ref.relationName || ref.table;
503
+ ones[relName] = { targetTable: ref.table, sourceColumn: col2.name };
504
+ }
505
+ }
506
+ for (const [otherName, otherTable] of Object.entries(this._schema)) {
507
+ const otherConfig = otherTable[TableConfigSymbol];
508
+ for (const [colName, col2] of Object.entries(otherConfig.columns)) {
509
+ if (col2.references && col2.references.table === tableName) {
510
+ const ref = col2.references;
511
+ const backRelName = ref.backRelationName || otherName;
512
+ manys[backRelName] = { targetTable: otherName, targetColumn: col2.name };
513
+ }
514
+ }
515
+ }
516
+ for (const [junctionName, junctionTable] of Object.entries(this._schema)) {
517
+ const junctionConfig = junctionTable[TableConfigSymbol];
518
+ const refs = Object.entries(junctionConfig.columns).filter(([_, c]) => c.references);
519
+ const toThis = refs.find(([_, c]) => c.references.table === tableName);
520
+ if (toThis) {
521
+ for (const [otherColName, otherCol] of refs) {
522
+ if (otherCol === toThis[1])
523
+ continue;
524
+ const ref = otherCol.references;
525
+ const targetTableName = ref.table;
526
+ const relName = toThis[1].references.backRelationName || targetTableName;
527
+ manyToManys[relName] = {
528
+ junctionTable: junctionName,
529
+ targetTable: targetTableName,
530
+ joinSourceColumn: toThis[1].name,
531
+ joinTargetColumn: otherCol.name
532
+ };
533
+ }
534
+ }
535
+ }
536
+ const result2 = { ones, manys, manyToManys };
537
+ schemaCache.set(tableName, result2);
538
+ return result2;
539
+ }
540
+ _buildSelectJson(tableName, args, alias, params, parentAlias, joinCondition, extraJoin) {
541
+ const tableConfig = this._schema[tableName][TableConfigSymbol];
542
+ const relations = this._getRuntimeRelations(tableName);
543
+ const withArgs = args.with || {};
544
+ const jsonFields = [];
545
+ const lateralJoins = [];
546
+ const columnsConfig = args.columns;
547
+ for (const [colKey, colConfig] of Object.entries(tableConfig.columns)) {
548
+ if (columnsConfig) {
549
+ if (columnsConfig[colKey] !== true) {
550
+ continue;
551
+ }
552
+ }
553
+ jsonFields.push(`'${colKey}', "${alias}"."${colConfig.name}"`);
554
+ }
555
+ for (const [relKey, relArgs] of Object.entries(withArgs)) {
556
+ const isTrue = relArgs === true;
557
+ const rArgs = isTrue ? {} : relArgs;
558
+ if (relations.ones[relKey]) {
559
+ const rel = relations.ones[relKey];
560
+ const subAlias = `${alias}_${relKey}`;
561
+ const joinCond = `"${subAlias}"."${this._schema[rel.targetTable][TableConfigSymbol].columns.id?.name ?? "id"}" = "${alias}"."${rel.sourceColumn}"`;
562
+ const subQuery = this._buildSelectJson(rel.targetTable, rArgs, subAlias, params, alias, joinCond);
563
+ const subSql = `SELECT ${subQuery.sql} ${subQuery.from}`;
564
+ jsonFields.push(`'${relKey}', (${subSql})`);
565
+ } else if (relations.manys[relKey]) {
566
+ const rel = relations.manys[relKey];
567
+ const subAlias = `${alias}_${relKey}`;
568
+ const joinCond = `"${subAlias}"."${rel.targetColumn}" = "${alias}"."${tableConfig.columns.id?.name ?? "id"}"`;
569
+ const subQuery = this._buildSelectJson(rel.targetTable, rArgs, subAlias, params, alias, joinCond);
570
+ const aggAlias = `${subAlias}_agg`;
571
+ const aggSql = `
572
+ SELECT COALESCE(json_agg(_sub.obj), '[]'::json)
573
+ FROM (
574
+ SELECT ${subQuery.sql} AS obj
575
+ ${subQuery.from}
576
+ ) _sub
577
+ `;
578
+ jsonFields.push(`'${relKey}', (${aggSql})`);
579
+ } else if (relations.manyToManys[relKey]) {
580
+ const rel = relations.manyToManys[relKey];
581
+ const subAlias = `${alias}_${relKey}`;
582
+ const junctionAlias = `${alias}_j_${relKey}`;
583
+ const targetTableConfig = this._schema[rel.targetTable][TableConfigSymbol];
584
+ const junctionTableConfig = this._schema[rel.junctionTable][TableConfigSymbol];
585
+ const fromExtra = `
586
+ INNER JOIN ${junctionTableConfig.qualifiedName} AS "${junctionAlias}"
587
+ ON "${junctionAlias}"."${rel.joinTargetColumn}" = "${subAlias}"."${targetTableConfig.columns.id?.name ?? "id"}"
588
+ `;
589
+ const whereExtra = `"${junctionAlias}"."${rel.joinSourceColumn}" = "${alias}"."${tableConfig.columns.id?.name ?? "id"}"`;
590
+ const subQuery = this._buildSelectJson(rel.targetTable, rArgs, subAlias, params, alias, whereExtra, fromExtra);
591
+ const aggSql = `
592
+ SELECT COALESCE(json_agg(_sub.obj), '[]'::json)
593
+ FROM (
594
+ SELECT ${subQuery.sql} AS obj
595
+ ${subQuery.from}
596
+ ) _sub
597
+ `;
598
+ jsonFields.push(`'${relKey}', (${aggSql})`);
599
+ }
600
+ }
601
+ let selectSql = `json_build_object(${jsonFields.join(", ")})`;
602
+ let fromSql = `FROM ${tableConfig.qualifiedName} AS "${alias}"`;
603
+ if (extraJoin) {
604
+ fromSql += ` ${extraJoin}`;
605
+ }
606
+ if (joinCondition) {
607
+ fromSql += ` WHERE ${joinCondition}`;
608
+ }
609
+ if (args.where && args.where.sql) {
610
+ const offset = params.length;
611
+ fromSql += (joinCondition ? " AND " : " WHERE ") + args.where.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
612
+ params.push(...args.where.params);
613
+ }
614
+ if (args.orderBy) {
615
+ if (typeof args.orderBy === "string") {
616
+ fromSql += ` ORDER BY ${args.orderBy}`;
617
+ } else if (args.orderBy.sql) {
618
+ const offset = params.length;
619
+ fromSql += ` ORDER BY ` + args.orderBy.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
620
+ params.push(...args.orderBy.params);
621
+ }
622
+ }
623
+ if (args.limit !== undefined) {
624
+ params.push(args.limit);
625
+ fromSql += ` LIMIT $${params.length}`;
626
+ }
627
+ if (args.offset !== undefined) {
628
+ params.push(args.offset);
629
+ fromSql += ` OFFSET $${params.length}`;
630
+ }
631
+ return { sql: selectSql, from: fromSql };
632
+ }
633
+ buildSQL(args) {
634
+ const params = [];
635
+ const rootAlias = "root";
636
+ const subQuery = this._buildSelectJson(this._tableName, args, rootAlias, params);
637
+ const sql2 = `SELECT ${subQuery.sql} AS _data ${subQuery.from}`;
638
+ return { sql: sql2, params };
639
+ }
640
+ }
641
+
642
+ // ../bungres-orm/src/db.ts
643
+ function parseDBName(url) {
644
+ try {
645
+ return new URL(url).pathname.slice(1);
646
+ } catch {
647
+ return "";
648
+ }
649
+ }
650
+ function maintenanceUrl(url) {
651
+ try {
652
+ const u = new URL(url);
653
+ u.pathname = "/postgres";
654
+ return u.toString();
655
+ } catch {
656
+ return url;
657
+ }
658
+ }
659
+ async function ensureDatabase(url) {
660
+ const dbName = parseDBName(url);
661
+ if (!dbName || dbName === "postgres")
662
+ return;
663
+ const maintenance = new Bun.SQL(maintenanceUrl(url), { max: 1 });
664
+ try {
665
+ const rows = await maintenance.unsafe(`SELECT 1 FROM pg_database WHERE datname = $1`, [dbName]);
666
+ if (rows.length === 0) {
667
+ if (!/^[a-zA-Z_][a-zA-Z0-9_$]*$/.test(dbName)) {
668
+ throw new Error(`Invalid database name: "${dbName}"`);
669
+ }
670
+ await maintenance.unsafe(`CREATE DATABASE "${dbName}"`);
671
+ console.log(`bungres: created database "${dbName}"`);
672
+ }
673
+ } finally {
674
+ await maintenance.end();
675
+ }
676
+ }
677
+
678
+ class BungresDB {
679
+ _sql;
680
+ _config;
681
+ _ready = null;
682
+ constructor(config2) {
683
+ const url = typeof config2 === "string" ? config2 : config2.url;
684
+ const opts = typeof config2 === "object" ? config2 : { url };
685
+ this._config = { autoCreateDB: true, ...opts };
686
+ this._sql = new Bun.SQL(url, {
687
+ max: opts.max ?? 10,
688
+ idleTimeout: opts.idleTimeout ?? 1e4,
689
+ ...opts.maxLifetime !== undefined && { maxLifetime: opts.maxLifetime },
690
+ ...opts.tls !== undefined && { tls: opts.tls }
691
+ });
692
+ if (this._config.autoCreateDB !== false) {
693
+ this._ready = ensureDatabase(url).catch((err) => {
694
+ console.warn(`bungres: could not auto-create database: ${err.message}`);
695
+ });
696
+ }
697
+ }
698
+ async ready() {
699
+ if (this._ready) {
700
+ await this._ready;
701
+ this._ready = null;
702
+ }
703
+ }
704
+ select(tableOrFields) {
705
+ if (tableOrFields) {
706
+ if (TableConfigSymbol in tableOrFields) {
707
+ return new SelectBuilder(tableOrFields, this);
708
+ }
709
+ return new SelectBuilderIntermediate(this, tableOrFields);
710
+ }
711
+ return new SelectBuilderIntermediate(this);
712
+ }
713
+ insert(table2) {
714
+ return new InsertBuilder(table2, this);
715
+ }
716
+ update(table2) {
717
+ return new UpdateBuilder(table2, this);
718
+ }
719
+ delete(table2) {
720
+ return new DeleteBuilder(table2, this);
721
+ }
722
+ async execute(builder) {
723
+ await this.ready();
724
+ const chunk = "toSQL" in builder ? builder.toSQL() : builder;
725
+ const result2 = await this._sql.unsafe(chunk.sql, chunk.params);
726
+ return Array.from(result2);
727
+ }
728
+ async executeSingle(builder) {
729
+ const rows = await this.execute(builder);
730
+ return rows[0] ?? null;
731
+ }
732
+ async raw(query, params = []) {
733
+ await this.ready();
734
+ const result2 = await this._sql.unsafe(query, params);
735
+ return Array.from(result2);
736
+ }
737
+ async transaction(fn) {
738
+ await this.ready();
739
+ return this._sql.transaction(async (txSql) => {
740
+ const tx = new BungresTransaction(txSql);
741
+ return fn(tx);
742
+ });
743
+ }
744
+ async close() {
745
+ await this._sql.end();
746
+ }
747
+ }
748
+
749
+ class BungresTransaction {
750
+ _sql;
751
+ constructor(sql2) {
752
+ this._sql = sql2;
753
+ }
754
+ select(tableOrFields) {
755
+ if (tableOrFields) {
756
+ if (TableConfigSymbol in tableOrFields) {
757
+ return new SelectBuilder(tableOrFields, this);
758
+ }
759
+ return new SelectBuilderIntermediate(this, tableOrFields);
760
+ }
761
+ return new SelectBuilderIntermediate(this);
762
+ }
763
+ insert(table2) {
764
+ return new InsertBuilder(table2, this);
765
+ }
766
+ update(table2) {
767
+ return new UpdateBuilder(table2, this);
768
+ }
769
+ delete(table2) {
770
+ return new DeleteBuilder(table2, this);
771
+ }
772
+ async execute(builder) {
773
+ const chunk = "toSQL" in builder ? builder.toSQL() : builder;
774
+ const result2 = await this._sql.unsafe(chunk.sql, chunk.params);
775
+ return Array.from(result2);
776
+ }
777
+ async executeSingle(builder) {
778
+ const rows = await this.execute(builder);
779
+ return rows[0] ?? null;
780
+ }
781
+ async raw(query, params = []) {
782
+ const result2 = await this._sql.unsafe(query, params);
783
+ return Array.from(result2);
784
+ }
785
+ }
786
+ function createDB(config2) {
787
+ const db2 = new BungresDB(config2);
788
+ if (typeof config2 === "object" && config2.schema) {
789
+ const schema = config2.schema;
790
+ return new Proxy(db2, {
791
+ get(target, prop) {
792
+ if (prop in target) {
793
+ const value = target[prop];
794
+ if (typeof value === "function") {
795
+ return value.bind(target);
796
+ }
797
+ return value;
798
+ }
799
+ if (typeof prop === "string" && prop in schema) {
800
+ return new RelationalQueryBuilder(target, schema, prop);
801
+ }
802
+ return;
803
+ }
804
+ });
805
+ }
806
+ return db2;
807
+ }
808
+ // ../bungres-orm/src/ddl.ts
809
+ function generateCreateTable(config2, ifNotExists = true) {
810
+ const tableName = config2.schema ? `"${config2.schema}"."${config2.name}"` : `"${config2.name}"`;
811
+ const exists = ifNotExists ? " IF NOT EXISTS" : "";
812
+ const columnDefs = Object.entries(config2.columns).map(([key, col2]) => buildColumnDDL(key, col2, config2.name));
813
+ if (config2.primaryKeys && config2.primaryKeys.length > 1) {
814
+ const pkCols = config2.primaryKeys.map((c) => `"${c}"`).join(", ");
815
+ columnDefs.push(`PRIMARY KEY (${pkCols})`);
816
+ }
817
+ if (config2.checks) {
818
+ for (const check of config2.checks) {
819
+ columnDefs.push(`CHECK (${check})`);
820
+ }
821
+ }
822
+ let sql2 = `CREATE TABLE${exists} ${tableName} (
823
+ ${columnDefs.join(`,
824
+ `)}
825
+ );`;
826
+ if (config2.indexes && config2.indexes.length > 0) {
827
+ sql2 += `
828
+
829
+ ` + config2.indexes.map((idx) => buildIndex(config2, idx)).join(`
830
+ `);
831
+ }
832
+ return sql2;
833
+ }
834
+ function generateAddConstraint(tableName, schema, constraintName, constraintDef) {
835
+ const tbl = schema ? `"${schema}"."${tableName}"` : `"${tableName}"`;
836
+ return `ALTER TABLE ${tbl} ADD CONSTRAINT "${constraintName}" ${constraintDef};`;
837
+ }
838
+ function generateDropConstraint(tableName, schema, constraintName) {
839
+ const tbl = schema ? `"${schema}"."${tableName}"` : `"${tableName}"`;
840
+ return `ALTER TABLE ${tbl} DROP CONSTRAINT IF EXISTS "${constraintName}";`;
841
+ }
842
+ function buildColumnDDL(key, col2, tableName) {
843
+ const name = `"${col2.name || key}"`;
844
+ let type = buildType(col2);
845
+ const parts = [`${name} ${type}`];
846
+ if (col2.primaryKey && !["serial", "bigserial"].includes(col2.dataType)) {
847
+ parts.push("PRIMARY KEY");
848
+ } else if (col2.primaryKey) {
849
+ parts.push("PRIMARY KEY");
850
+ }
851
+ if (col2.notNull && !col2.primaryKey && !["serial", "bigserial"].includes(col2.dataType)) {
852
+ parts.push("NOT NULL");
853
+ }
854
+ if (col2.unique && !col2.primaryKey) {
855
+ parts.push("UNIQUE");
856
+ }
857
+ if (col2.defaultFn) {
858
+ parts.push(`DEFAULT ${col2.defaultFn}`);
859
+ } else if (col2.defaultValue !== undefined) {
860
+ parts.push(`DEFAULT ${formatDefaultValue(col2.defaultValue, col2.dataType)}`);
861
+ }
862
+ if (col2.references) {
863
+ const ref = col2.references;
864
+ const constraintName = `${tableName}_${col2.name || key}_fkey`;
865
+ let fk = `CONSTRAINT "${constraintName}" REFERENCES "${ref.table}"("${ref.column}")`;
866
+ if (ref.onDelete)
867
+ fk += ` ON DELETE ${ref.onDelete.toUpperCase()}`;
868
+ if (ref.onUpdate)
869
+ fk += ` ON UPDATE ${ref.onUpdate.toUpperCase()}`;
870
+ parts.push(fk);
871
+ }
872
+ if (col2.check) {
873
+ parts.push(`CHECK (${col2.check})`);
874
+ }
875
+ return parts.join(" ");
876
+ }
877
+ function buildType(col2) {
878
+ switch (col2.dataType) {
879
+ case "varchar":
880
+ return col2.length ? `VARCHAR(${col2.length})` : "VARCHAR";
881
+ case "char":
882
+ return col2.length ? `CHAR(${col2.length})` : "CHAR";
883
+ case "numeric":
884
+ case "decimal":
885
+ return col2.dataType.toUpperCase();
886
+ case "double precision":
887
+ return "DOUBLE PRECISION";
888
+ case "timestamptz":
889
+ return "TIMESTAMP WITH TIME ZONE";
890
+ case "timetz":
891
+ return "TIME WITH TIME ZONE";
892
+ default:
893
+ return col2.dataType.toUpperCase();
894
+ }
895
+ }
896
+ function formatDefaultValue(value, dataType) {
897
+ if (value === null)
898
+ return "NULL";
899
+ if (typeof value === "string") {
900
+ if (dataType === "boolean")
901
+ return value;
902
+ return `'${value.replace(/'/g, "''")}'`;
903
+ }
904
+ if (typeof value === "boolean")
905
+ return value ? "TRUE" : "FALSE";
906
+ if (typeof value === "number")
907
+ return String(value);
908
+ return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
909
+ }
910
+ function buildIndex(table2, idx) {
911
+ const tableName = table2.schema ? `"${table2.schema}"."${table2.name}"` : `"${table2.name}"`;
912
+ const unique = idx.unique ? "UNIQUE " : "";
913
+ const using = idx.using ? ` USING ${idx.using.toUpperCase()}` : "";
914
+ const cols = idx.columns.map((c) => `"${c}"`).join(", ");
915
+ const where = idx.where ? ` WHERE ${idx.where}` : "";
916
+ const idxName = idx.name ?? `idx_${table2.name}_${idx.columns.join("_")}`;
917
+ return `CREATE ${unique}INDEX IF NOT EXISTS "${idxName}" ON ${tableName}${using} (${cols})${where};`;
918
+ }
919
+ function generateAddColumn(tableName, schema, key, col2) {
920
+ const tbl = schema ? `"${schema}"."${tableName}"` : `"${tableName}"`;
921
+ return `ALTER TABLE ${tbl} ADD COLUMN IF NOT EXISTS ${buildColumnDDL(key, col2, tableName)};`;
922
+ }
923
+ function generateDropColumn(tableName, schema, columnName) {
924
+ const tbl = schema ? `"${schema}"."${tableName}"` : `"${tableName}"`;
925
+ return `ALTER TABLE ${tbl} DROP COLUMN IF EXISTS "${columnName}";`;
926
+ }
927
+ // src/differ.ts
928
+ function diffSchemas(prev, next) {
929
+ const statements = [];
930
+ const summary = [];
931
+ const warnings = [];
932
+ const prevTables = new Set(Object.keys(prev));
933
+ const nextTables = new Set(Object.keys(next));
934
+ const newTableConfigs = [];
935
+ for (const tableName of nextTables) {
936
+ if (!prevTables.has(tableName)) {
937
+ newTableConfigs.push(next[tableName]);
938
+ }
939
+ }
940
+ for (const config2 of topoSortConfigs(newTableConfigs)) {
941
+ statements.push(generateCreateTable(config2, true));
942
+ summary.push(`CREATE TABLE ${config2.name}`);
943
+ }
944
+ for (const tableName of prevTables) {
945
+ if (!nextTables.has(tableName)) {
946
+ const config2 = prev[tableName];
947
+ const tbl = config2.schema ? `"${config2.schema}"."${tableName}"` : `"${tableName}"`;
948
+ statements.push(`DROP TABLE IF EXISTS ${tbl};`);
949
+ summary.push(`DROP TABLE ${tableName}`);
950
+ warnings.push(`Data loss warning: Table '${tableName}' will be permanently deleted.`);
951
+ }
952
+ }
953
+ for (const tableName of nextTables) {
954
+ if (!prevTables.has(tableName))
955
+ continue;
956
+ const prevConfig = prev[tableName];
957
+ const nextConfig = next[tableName];
958
+ const prevCols = prevConfig.columns;
959
+ const nextCols = nextConfig.columns;
960
+ const prevColNames = new Set(Object.keys(prevCols));
961
+ const nextColNames = new Set(Object.keys(nextCols));
962
+ for (const key of nextColNames) {
963
+ if (!prevColNames.has(key)) {
964
+ const col2 = nextCols[key];
965
+ statements.push(generateAddColumn(tableName, nextConfig.schema, key, col2));
966
+ summary.push(`ALTER TABLE ${tableName} ADD COLUMN ${col2.name}`);
967
+ }
968
+ }
969
+ for (const key of prevColNames) {
970
+ if (!nextColNames.has(key)) {
971
+ const col2 = prevCols[key];
972
+ statements.push(generateDropColumn(tableName, prevConfig.schema, col2.name));
973
+ summary.push(`ALTER TABLE ${tableName} DROP COLUMN ${col2.name}`);
974
+ warnings.push(`Data loss warning: Column '${col2.name}' in table '${tableName}' will be permanently deleted.`);
975
+ }
976
+ }
977
+ for (const key of nextColNames) {
978
+ if (!prevColNames.has(key))
979
+ continue;
980
+ const changes = diffColumn(prevCols[key], nextCols[key]);
981
+ if (changes.length > 0) {
982
+ const tbl = nextConfig.schema ? `"${nextConfig.schema}"."${tableName}"` : `"${tableName}"`;
983
+ for (const change of changes) {
984
+ statements.push(`ALTER TABLE ${tbl} ${change};`);
985
+ summary.push(`ALTER TABLE ${tableName} ALTER COLUMN ${nextCols[key].name} (${change})`);
986
+ }
987
+ }
988
+ const prevRef = prevCols[key].references;
989
+ const nextRef = nextCols[key].references;
990
+ if (JSON.stringify(prevRef) !== JSON.stringify(nextRef)) {
991
+ const constraintName = `${tableName}_${nextCols[key].name}_fkey`;
992
+ if (prevRef) {
993
+ statements.push(generateDropConstraint(tableName, nextConfig.schema, constraintName));
994
+ summary.push(`ALTER TABLE ${tableName} DROP CONSTRAINT ${constraintName}`);
995
+ }
996
+ if (nextRef) {
997
+ let fkDef = `FOREIGN KEY ("${nextCols[key].name}") REFERENCES "${nextRef.table}"("${nextRef.column}")`;
998
+ if (nextRef.onDelete)
999
+ fkDef += ` ON DELETE ${nextRef.onDelete.toUpperCase()}`;
1000
+ if (nextRef.onUpdate)
1001
+ fkDef += ` ON UPDATE ${nextRef.onUpdate.toUpperCase()}`;
1002
+ statements.push(generateAddConstraint(tableName, nextConfig.schema, constraintName, fkDef));
1003
+ summary.push(`ALTER TABLE ${tableName} ADD CONSTRAINT ${constraintName}`);
1004
+ }
1005
+ }
1006
+ }
1007
+ const prevIdxNames = new Set((prevConfig.indexes ?? []).map((i) => i.name ?? `idx_${tableName}_${i.columns.join("_")}`));
1008
+ for (const idx of nextConfig.indexes ?? []) {
1009
+ const idxName = idx.name ?? `idx_${tableName}_${idx.columns.join("_")}`;
1010
+ if (!prevIdxNames.has(idxName)) {
1011
+ const tbl = nextConfig.schema ? `"${nextConfig.schema}"."${tableName}"` : `"${tableName}"`;
1012
+ const unique = idx.unique ? "UNIQUE " : "";
1013
+ const using = idx.using ? ` USING ${idx.using.toUpperCase()}` : "";
1014
+ const cols = idx.columns.map((c) => `"${c}"`).join(", ");
1015
+ const where = idx.where ? ` WHERE ${idx.where}` : "";
1016
+ statements.push(`CREATE ${unique}INDEX IF NOT EXISTS "${idxName}" ON ${tbl}${using} (${cols})${where};`);
1017
+ summary.push(`CREATE INDEX ${idxName} ON ${tableName}`);
1018
+ }
1019
+ }
1020
+ const nextIdxNames = new Set((nextConfig.indexes ?? []).map((i) => i.name ?? `idx_${tableName}_${i.columns.join("_")}`));
1021
+ for (const idx of prevConfig.indexes ?? []) {
1022
+ const idxName = idx.name ?? `idx_${tableName}_${idx.columns.join("_")}`;
1023
+ if (!nextIdxNames.has(idxName)) {
1024
+ statements.push(`DROP INDEX IF EXISTS "${idxName}";`);
1025
+ summary.push(`DROP INDEX ${idxName}`);
1026
+ }
1027
+ }
1028
+ }
1029
+ return { statements, summary, warnings };
1030
+ }
1031
+ function topoSortConfigs(tables) {
1032
+ const byName = new Map(tables.map((t) => [t.name, t]));
1033
+ const visited = new Set;
1034
+ const result2 = [];
1035
+ function deps(config2) {
1036
+ return Object.values(config2.columns).map((col2) => col2.references?.table).filter((t) => t !== undefined && byName.has(t) && t !== config2.name);
1037
+ }
1038
+ function visit(name) {
1039
+ if (visited.has(name))
1040
+ return;
1041
+ visited.add(name);
1042
+ const config2 = byName.get(name);
1043
+ if (!config2)
1044
+ return;
1045
+ for (const dep of deps(config2))
1046
+ visit(dep);
1047
+ result2.push(config2);
1048
+ }
1049
+ for (const table2 of tables)
1050
+ visit(table2.name);
1051
+ return result2;
1052
+ }
1053
+ function diffColumn(prev, next) {
1054
+ const changes = [];
1055
+ const col2 = `"${next.name}"`;
1056
+ if (prev.dataType !== next.dataType) {
1057
+ changes.push(`ALTER COLUMN ${col2} TYPE ${next.dataType.toUpperCase()} USING ${col2}::${next.dataType}`);
1058
+ }
1059
+ if (!prev.notNull && next.notNull) {
1060
+ changes.push(`ALTER COLUMN ${col2} SET NOT NULL`);
1061
+ }
1062
+ if (prev.notNull && !next.notNull) {
1063
+ changes.push(`ALTER COLUMN ${col2} DROP NOT NULL`);
1064
+ }
1065
+ const prevDef = prev.defaultFn ?? (prev.defaultValue !== undefined ? String(prev.defaultValue) : undefined);
1066
+ const nextDef = next.defaultFn ?? (next.defaultValue !== undefined ? String(next.defaultValue) : undefined);
1067
+ if (prevDef !== nextDef) {
1068
+ if (nextDef !== undefined) {
1069
+ const val = next.defaultFn ?? formatDefault(next.defaultValue, next.dataType);
1070
+ changes.push(`ALTER COLUMN ${col2} SET DEFAULT ${val}`);
1071
+ } else {
1072
+ changes.push(`ALTER COLUMN ${col2} DROP DEFAULT`);
1073
+ }
1074
+ }
1075
+ return changes;
1076
+ }
1077
+ function formatDefault(value, dataType) {
1078
+ if (value === null || value === undefined)
1079
+ return "NULL";
1080
+ if (typeof value === "boolean")
1081
+ return value ? "TRUE" : "FALSE";
1082
+ if (typeof value === "number")
1083
+ return String(value);
1084
+ if (typeof value === "string") {
1085
+ if (dataType === "boolean")
1086
+ return value;
1087
+ return `'${value.replace(/'/g, "''")}'`;
1088
+ }
1089
+ return `'${JSON.stringify(value)}'`;
1090
+ }
1091
+
1092
+ // src/schema-loader.ts
1093
+ import { resolve as resolve2, join as join2 } from "path";
1094
+ async function loadSchemas(patterns, cwd = process.cwd()) {
1095
+ const globs = Array.isArray(patterns) ? patterns : [patterns];
1096
+ const entries = [];
1097
+ for (const pattern of globs) {
1098
+ const glob = new Bun.Glob(pattern);
1099
+ for await (const file of glob.scan({ cwd, absolute: false })) {
1100
+ const absPath = resolve2(join2(cwd, file));
1101
+ const mod = await import(absPath);
1102
+ for (const [exportName, value] of Object.entries(mod)) {
1103
+ if (isTable(value)) {
1104
+ entries.push({
1105
+ exportName,
1106
+ config: value[TableConfigSymbol],
1107
+ table: value,
1108
+ filePath: absPath
1109
+ });
1110
+ }
1111
+ }
1112
+ }
1113
+ }
1114
+ return entries;
1115
+ }
1116
+ function isTable(value) {
1117
+ return typeof value === "object" && value !== null && TableConfigSymbol in value;
1118
+ }
1119
+
1120
+ // src/commands/push.ts
1121
+ async function runPush(config2, opts = {}) {
1122
+ console.log("@bungres/kit push: loading schemas...");
1123
+ const schemas2 = await loadSchemas(config2.schema);
1124
+ if (schemas2.length === 0) {
1125
+ console.warn("No table definitions found. Check your schema glob pattern.");
1126
+ return;
1127
+ }
1128
+ const sql2 = new Bun.SQL(config2.dbUrl);
1129
+ try {
1130
+ await sql2.unsafe(`
1131
+ CREATE TABLE IF NOT EXISTS public.__bungres_push (
1132
+ id SERIAL PRIMARY KEY,
1133
+ snapshot JSONB NOT NULL
1134
+ );
1135
+ `);
1136
+ const rows = await sql2.unsafe(`SELECT snapshot FROM public.__bungres_push ORDER BY id DESC LIMIT 1;`);
1137
+ let prevSnapshot = {};
1138
+ if (rows.length > 0) {
1139
+ prevSnapshot = typeof rows[0].snapshot === "string" ? JSON.parse(rows[0].snapshot) : rows[0].snapshot;
1140
+ }
1141
+ const currentSnapshot = Object.fromEntries(schemas2.map((s) => [s.config.name, s.config]));
1142
+ const diff = diffSchemas(prevSnapshot, currentSnapshot);
1143
+ if (diff.statements.length === 0) {
1144
+ console.log(`
1145
+ No schema changes detected. Database is up to date.`);
1146
+ return;
1147
+ }
1148
+ console.log(`
1149
+ Changes to apply:`);
1150
+ for (const s of diff.summary)
1151
+ console.log(` + ${s}`);
1152
+ if (diff.warnings && diff.warnings.length > 0) {
1153
+ console.warn(`
1154
+ \u26A0\uFE0F WARNING: Data Loss Detected!`);
1155
+ for (const w of diff.warnings)
1156
+ console.warn(` ! ${w}`);
1157
+ console.warn(`
1158
+ These changes will be immediately executed against the database!`);
1159
+ }
1160
+ if (!opts.force) {
1161
+ process.stdout.write(`
1162
+ Are you sure you want to push these changes? Type YES to continue: `);
1163
+ const answer = await readLine();
1164
+ if (answer.trim().toLowerCase() !== "yes") {
1165
+ console.log("Aborted.");
1166
+ return;
1167
+ }
1168
+ }
1169
+ console.log(`
1170
+ Pushing changes...`);
1171
+ await sql2.unsafe(`CREATE EXTENSION IF NOT EXISTS "pgcrypto";`);
1172
+ for (const stmt of diff.statements) {
1173
+ if (config2.verbose) {
1174
+ console.log(`-- ${stmt}`);
1175
+ }
1176
+ await sql2.unsafe(stmt);
1177
+ }
1178
+ await sql2.unsafe(`INSERT INTO public.__bungres_push (snapshot) VALUES ($1);`, [JSON.stringify(currentSnapshot)]);
1179
+ console.log(`
1180
+ Push complete.`);
1181
+ } finally {
1182
+ await sql2.end();
1183
+ }
1184
+ }
1185
+ async function readLine() {
1186
+ const buf = Buffer.alloc(256);
1187
+ const n = fs.readSync(0, buf, 0, 256, null);
1188
+ return buf.subarray(0, n).toString().trim();
1189
+ }
1190
+
1191
+ // src/commands/generate.ts
1192
+ import { join as join3, resolve as resolve3 } from "path";
1193
+
1194
+ // src/utils/colors.ts
1195
+ function colorize(text2, color) {
1196
+ const code2 = Bun.color(color, "ansi");
1197
+ return code2 ? `${code2}${text2}\x1B[0m` : text2;
1198
+ }
1199
+
1200
+ // src/commands/generate.ts
1201
+ var SNAPSHOT_FILE = ".snapshot.json";
1202
+ async function runGenerate(config2, name) {
1203
+ console.log("@bungres/kit generate: loading schemas...");
1204
+ const schemas2 = await loadSchemas(config2.schema);
1205
+ if (schemas2.length === 0) {
1206
+ console.warn(colorize("No table definitions found. Check your schema glob pattern.", "yellow"));
1207
+ return;
1208
+ }
1209
+ const migrationsDir = resolve3(config2.migrationsDir);
1210
+ await Bun.$`mkdir -p ${migrationsDir}`.quiet();
1211
+ const currentSnapshot = Object.fromEntries(schemas2.map((s) => [s.config.name, s.config]));
1212
+ const snapshotPath = join3(migrationsDir, SNAPSHOT_FILE);
1213
+ const snapshotFile = Bun.file(snapshotPath);
1214
+ const isFirstMigration = !await snapshotFile.exists();
1215
+ const prevSnapshot = isFirstMigration ? {} : JSON.parse(await snapshotFile.text());
1216
+ const now = new Date;
1217
+ const yyyy = now.getUTCFullYear();
1218
+ const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
1219
+ const dd = String(now.getUTCDate()).padStart(2, "0");
1220
+ const HH = String(now.getUTCHours()).padStart(2, "0");
1221
+ const MM = String(now.getUTCMinutes()).padStart(2, "0");
1222
+ const SS = String(now.getUTCSeconds()).padStart(2, "0");
1223
+ const prefix = `${yyyy}_${mm}_${dd}_${HH}${MM}${SS}`;
1224
+ const filename = name ? `${prefix}_${name}.sql` : `${prefix}.sql`;
1225
+ const outPath = join3(migrationsDir, filename);
1226
+ let statements;
1227
+ let summary;
1228
+ let warnings = [];
1229
+ if (isFirstMigration) {
1230
+ const sorted = topoSort(schemas2);
1231
+ statements = [
1232
+ `CREATE EXTENSION IF NOT EXISTS "pgcrypto";`,
1233
+ ``,
1234
+ ...sorted.flatMap((entry) => [
1235
+ `-- ${entry.exportName}`,
1236
+ generateCreateTable(entry.config, true),
1237
+ ``
1238
+ ])
1239
+ ];
1240
+ summary = sorted.map((s) => `CREATE TABLE ${s.config.name}`);
1241
+ } else {
1242
+ const diff = diffSchemas(prevSnapshot, currentSnapshot);
1243
+ if (diff.statements.length === 0) {
1244
+ console.log(colorize(`
1245
+ No schema changes detected. Nothing to generate.`, "yellow"));
1246
+ return;
1247
+ }
1248
+ statements = diff.statements;
1249
+ summary = diff.summary;
1250
+ warnings = diff.warnings;
1251
+ }
1252
+ const lines = [
1253
+ `-- Migration: ${filename}`,
1254
+ `-- Generated by @bungres/kit at ${new Date().toISOString()}`,
1255
+ `-- Changes: ${summary.join(", ")}`,
1256
+ ``,
1257
+ ...statements
1258
+ ];
1259
+ await Bun.write(outPath, lines.join(`
1260
+ `));
1261
+ await Bun.write(snapshotPath, JSON.stringify(currentSnapshot, null, 2));
1262
+ console.log(colorize(`
1263
+ Generated: ${outPath}`, "green"));
1264
+ console.log(` Changes:`);
1265
+ for (const s of summary)
1266
+ console.log(colorize(` + ${s}`, "cyan"));
1267
+ if (warnings.length > 0) {
1268
+ console.warn(colorize(`
1269
+ \u26A0\uFE0F WARNING: Data Loss Detected!`, "red"));
1270
+ for (const w of warnings)
1271
+ console.warn(colorize(` ! ${w}`, "red"));
1272
+ console.warn(colorize(` Please review the generated migration carefully before applying it.
1273
+ `, "yellow"));
1274
+ }
1275
+ console.log(colorize(`
1276
+ Run \`bungres migrate\` to apply it.`, "cyan"));
1277
+ }
1278
+ function topoSort(schemas2) {
1279
+ const byName = new Map(schemas2.map((s) => [s.config.name, s]));
1280
+ function deps(config2) {
1281
+ return Object.values(config2.columns).map((col2) => col2.references?.table).filter((t) => t !== undefined && byName.has(t) && t !== config2.name);
1282
+ }
1283
+ const visited = new Set;
1284
+ const result2 = [];
1285
+ function visit(name) {
1286
+ if (visited.has(name))
1287
+ return;
1288
+ visited.add(name);
1289
+ const entry = byName.get(name);
1290
+ if (!entry)
1291
+ return;
1292
+ for (const dep of deps(entry.config))
1293
+ visit(dep);
1294
+ result2.push(entry);
1295
+ }
1296
+ for (const schema of schemas2)
1297
+ visit(schema.config.name);
1298
+ return result2;
1299
+ }
1300
+
1301
+ // src/commands/migrate.ts
1302
+ import { join as join4, resolve as resolve4 } from "path";
1303
+ var MIGRATIONS_TABLE = "__bungres_migrations";
1304
+ var CREATE_MIGRATIONS_TABLE = `
1305
+ CREATE TABLE IF NOT EXISTS "${MIGRATIONS_TABLE}" (
1306
+ id SERIAL PRIMARY KEY,
1307
+ name TEXT NOT NULL UNIQUE,
1308
+ applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
1309
+ );
1310
+ `.trim();
1311
+ async function runMigrate(config2) {
1312
+ const migrationsDir = resolve4(config2.migrationsDir);
1313
+ const sql2 = new Bun.SQL(config2.dbUrl);
1314
+ try {
1315
+ await sql2.unsafe(CREATE_MIGRATIONS_TABLE);
1316
+ const glob = new Bun.Glob("*.sql");
1317
+ const files = [];
1318
+ for await (const file of glob.scan({ cwd: migrationsDir, absolute: false })) {
1319
+ files.push(file);
1320
+ }
1321
+ files.sort();
1322
+ if (files.length === 0) {
1323
+ console.log(colorize("No migration files found in " + migrationsDir, "yellow"));
1324
+ console.log(colorize("Run `bungres generate` first.", "yellow"));
1325
+ return;
1326
+ }
1327
+ const applied = await sql2.unsafe(`SELECT name FROM "${MIGRATIONS_TABLE}"`);
1328
+ const appliedSet = new Set(applied.map((r) => r.name));
1329
+ const pending = files.filter((f) => !appliedSet.has(f));
1330
+ if (pending.length === 0) {
1331
+ console.log(colorize("Everything is up to date.", "green"));
1332
+ return;
1333
+ }
1334
+ console.log(colorize(`
1335
+ Running ${pending.length} pending migration(s)...
1336
+ `, "cyan"));
1337
+ for (const file of pending) {
1338
+ const content = await Bun.file(join4(migrationsDir, file)).text();
1339
+ if (config2.verbose) {
1340
+ console.log(`-- ${file} --
1341
+ ${content}
1342
+ `);
1343
+ }
1344
+ await sql2.transaction(async (txSql) => {
1345
+ const statements = content.split(";").map((s) => s.trim()).filter(Boolean);
1346
+ for (const stmt of statements) {
1347
+ await txSql.unsafe(stmt + ";");
1348
+ }
1349
+ await txSql.unsafe(`INSERT INTO "${MIGRATIONS_TABLE}" (name) VALUES ($1)`, [file]);
1350
+ });
1351
+ console.log(colorize(` \u2713 ${file}`, "green"));
1352
+ }
1353
+ console.log(colorize(`
1354
+ Done.`, "green"));
1355
+ } finally {
1356
+ await sql2.end();
1357
+ }
1358
+ }
1359
+
1360
+ // src/commands/pull.ts
1361
+ import { resolve as resolve5, join as join5 } from "path";
1362
+ async function introspectDb(sql2, dbSchema) {
1363
+ const columns = await sql2.unsafe(`SELECT
1364
+ c.table_name,
1365
+ c.column_name,
1366
+ c.data_type,
1367
+ c.udt_name,
1368
+ c.is_nullable,
1369
+ c.column_default,
1370
+ c.character_maximum_length
1371
+ FROM information_schema.columns c
1372
+ WHERE c.table_schema = $1
1373
+ AND c.table_name NOT IN ('__bungres_migrations', '__bungres_push')
1374
+ ORDER BY c.table_name, c.ordinal_position`, [dbSchema]);
1375
+ const constraints = await sql2.unsafe(`SELECT
1376
+ tc.table_name,
1377
+ kcu.column_name,
1378
+ tc.constraint_type,
1379
+ ccu.table_name AS foreign_table,
1380
+ ccu.column_name AS foreign_column,
1381
+ rc.delete_rule,
1382
+ rc.update_rule
1383
+ FROM information_schema.table_constraints tc
1384
+ JOIN information_schema.key_column_usage kcu
1385
+ ON tc.constraint_name = kcu.constraint_name
1386
+ AND tc.table_schema = kcu.table_schema
1387
+ LEFT JOIN information_schema.constraint_column_usage ccu
1388
+ ON tc.constraint_name = ccu.constraint_name
1389
+ AND tc.table_schema = ccu.table_schema
1390
+ LEFT JOIN information_schema.referential_constraints rc
1391
+ ON tc.constraint_name = rc.constraint_name
1392
+ AND tc.table_schema = rc.constraint_schema
1393
+ WHERE tc.table_schema = $1`, [dbSchema]);
1394
+ const indexes = await sql2.unsafe(`SELECT tablename, indexname, indexdef
1395
+ FROM pg_indexes
1396
+ WHERE schemaname = $1`, [dbSchema]);
1397
+ return groupByTable(columns, constraints, indexes);
1398
+ }
1399
+ async function runPull(config2) {
1400
+ console.log("@bungres/kit pull: introspecting database...");
1401
+ const sql2 = new Bun.SQL(config2.dbUrl);
1402
+ try {
1403
+ const dbSchema = config2.dbSchema;
1404
+ const tableMap = await introspectDb(sql2, dbSchema);
1405
+ if (tableMap.size === 0) {
1406
+ console.log("No tables found in schema:", dbSchema);
1407
+ return;
1408
+ }
1409
+ const outDir = resolve5(config2.outDir);
1410
+ await Bun.$`mkdir -p ${outDir}`.quiet();
1411
+ const outPath = join5(outDir, "schema.ts");
1412
+ const code2 = generateSchemaTS(tableMap, dbSchema);
1413
+ await Bun.write(outPath, code2);
1414
+ console.log(`Generated schema: ${outPath}`);
1415
+ console.log(` Tables: ${[...tableMap.keys()].join(", ")}`);
1416
+ } finally {
1417
+ await sql2.end();
1418
+ }
1419
+ }
1420
+ function groupByTable(columns, constraints, indexes) {
1421
+ const map = new Map;
1422
+ for (const col2 of columns) {
1423
+ if (!map.has(col2.table_name)) {
1424
+ map.set(col2.table_name, {
1425
+ tableName: col2.table_name,
1426
+ columns: [],
1427
+ indexes: indexes.filter((i) => i.tablename === col2.table_name)
1428
+ });
1429
+ }
1430
+ const tableConstraints = constraints.filter((c) => c.table_name === col2.table_name && c.column_name === col2.column_name);
1431
+ const pkConstraint = tableConstraints.find((c) => c.constraint_type === "PRIMARY KEY");
1432
+ const uniqueConstraint = tableConstraints.find((c) => c.constraint_type === "UNIQUE");
1433
+ const fkConstraint = tableConstraints.find((c) => c.constraint_type === "FOREIGN KEY");
1434
+ map.get(col2.table_name).columns.push({
1435
+ name: col2.column_name,
1436
+ dataType: col2.data_type,
1437
+ udtName: col2.udt_name,
1438
+ isNullable: col2.is_nullable === "YES",
1439
+ columnDefault: col2.column_default,
1440
+ maxLength: col2.character_maximum_length,
1441
+ isPrimary: !!pkConstraint,
1442
+ isUnique: !!uniqueConstraint,
1443
+ foreignTable: fkConstraint?.foreign_table ?? undefined,
1444
+ foreignColumn: fkConstraint?.foreign_column ?? undefined,
1445
+ deleteRule: fkConstraint?.delete_rule ?? undefined,
1446
+ updateRule: fkConstraint?.update_rule ?? undefined
1447
+ });
1448
+ }
1449
+ return map;
1450
+ }
1451
+ function generateSchemaTS(tableMap, dbSchema) {
1452
+ const lines = [
1453
+ `// Generated by @bungres/kit pull`,
1454
+ `// Do not edit manually \u2014 re-run \`bungres pull\` to regenerate`,
1455
+ `// Generated at: ${new Date().toISOString()}`,
1456
+ ``,
1457
+ `import {`,
1458
+ ` table,`,
1459
+ ` text, varchar, char, integer, bigint, smallint,`,
1460
+ ` serial, bigserial, boolean, real, doublePrecision,`,
1461
+ ` numeric, decimal, json, jsonb,`,
1462
+ ` timestamp, timestamptz, date, time, uuid,`,
1463
+ ` bytea, inet,`,
1464
+ `} from "@bungres/orm";`,
1465
+ ``
1466
+ ];
1467
+ for (const [, table2] of tableMap) {
1468
+ const varName = toCamelCase(table2.tableName);
1469
+ lines.push(`export const ${varName} = table("${table2.tableName}", {`);
1470
+ for (const col2 of table2.columns) {
1471
+ const colExpr = buildColumnExpression(col2);
1472
+ lines.push(` ${col2.name}: ${colExpr},`);
1473
+ }
1474
+ const options = [];
1475
+ if (dbSchema !== "public") {
1476
+ options.push(`schema: "${dbSchema}"`);
1477
+ }
1478
+ if (table2.indexes.length > 0) {
1479
+ const idxLines = [];
1480
+ for (const idx of table2.indexes) {
1481
+ const m = idx.indexdef.match(/CREATE (UNIQUE )?INDEX (.+) ON (.+) USING (\w+) \((.+)\)(?: WHERE (.+))?/i);
1482
+ if (m) {
1483
+ const isUnique = !!m[1];
1484
+ const name = m[2].trim().replace(/^"|"$/g, "");
1485
+ if (name.endsWith("_pkey") || name.endsWith("_key"))
1486
+ continue;
1487
+ const using = m[4].toLowerCase();
1488
+ const cols = m[5].split(",").map((c) => `"${c.trim().replace(/^"|"$/g, "")}"`);
1489
+ let idxStr = `{ name: "${name}", columns: [${cols.join(", ")}], using: "${using}"`;
1490
+ if (isUnique)
1491
+ idxStr += `, unique: true`;
1492
+ if (m[6])
1493
+ idxStr += `, where: \`${m[6].trim()}\``;
1494
+ idxStr += ` }`;
1495
+ idxLines.push(idxStr);
1496
+ }
1497
+ }
1498
+ if (idxLines.length > 0) {
1499
+ options.push(`indexes: [
1500
+ ${idxLines.join(`,
1501
+ `)}
1502
+ ]`);
1503
+ }
1504
+ }
1505
+ if (options.length > 0) {
1506
+ lines.push(`}, {`);
1507
+ lines.push(` ${options.join(`,
1508
+ `)}`);
1509
+ lines.push(`});`);
1510
+ } else {
1511
+ lines.push(`});`);
1512
+ }
1513
+ lines.push(``);
1514
+ }
1515
+ return lines.join(`
1516
+ `);
1517
+ }
1518
+ function buildColumnExpression(col2) {
1519
+ let expr = pgTypeToBungresBuilder(col2);
1520
+ if (col2.isPrimary)
1521
+ expr += `.primaryKey()`;
1522
+ else if (!col2.isNullable)
1523
+ expr += `.notNull()`;
1524
+ if (col2.isUnique && !col2.isPrimary)
1525
+ expr += `.unique()`;
1526
+ if (col2.columnDefault !== null && !col2.isPrimary) {
1527
+ if (col2.columnDefault.includes("(")) {
1528
+ expr += `.defaultRaw("${col2.columnDefault}")`;
1529
+ } else if (col2.dataType === "boolean") {
1530
+ expr += `.default(${col2.columnDefault})`;
1531
+ } else if (col2.dataType.includes("int") || col2.dataType.includes("numeric") || col2.dataType.includes("real") || col2.dataType.includes("double")) {
1532
+ expr += `.default(${col2.columnDefault})`;
1533
+ } else {
1534
+ const cleaned = col2.columnDefault.replace(/^'(.*)'::.*$/, "$1");
1535
+ expr += `.default("${cleaned}")`;
1536
+ }
1537
+ }
1538
+ if (col2.foreignTable && col2.foreignColumn) {
1539
+ const deleteRule = col2.deleteRule?.toLowerCase().replace(" ", " ");
1540
+ const updateRule = col2.updateRule?.toLowerCase().replace(" ", " ");
1541
+ let refOpts = "";
1542
+ if (deleteRule && deleteRule !== "no action") {
1543
+ refOpts += `onDelete: "${deleteRule}", `;
1544
+ }
1545
+ if (updateRule && updateRule !== "no action") {
1546
+ refOpts += `onUpdate: "${updateRule}"`;
1547
+ }
1548
+ const opts = refOpts ? `, { ${refOpts.trim().replace(/,$/, "")} }` : "";
1549
+ expr += `.references("${col2.foreignTable}", "${col2.foreignColumn}"${opts})`;
1550
+ }
1551
+ return expr;
1552
+ }
1553
+ function pgTypeToBungresBuilder(col2) {
1554
+ const dt = col2.dataType;
1555
+ const name = col2.name;
1556
+ if (dt === "uuid")
1557
+ return `uuid("${name}")`;
1558
+ if (dt === "text")
1559
+ return `text("${name}")`;
1560
+ if (dt === "character varying")
1561
+ return col2.maxLength ? `varchar("${name}", ${col2.maxLength})` : `varchar("${name}")`;
1562
+ if (dt === "character")
1563
+ return col2.maxLength ? `char("${name}", ${col2.maxLength})` : `char("${name}")`;
1564
+ if (dt === "integer")
1565
+ return `integer("${name}")`;
1566
+ if (dt === "bigint")
1567
+ return `bigint("${name}")`;
1568
+ if (dt === "smallint")
1569
+ return `smallint("${name}")`;
1570
+ if (dt === "boolean")
1571
+ return `boolean("${name}")`;
1572
+ if (dt === "real")
1573
+ return `real("${name}")`;
1574
+ if (dt === "double precision")
1575
+ return `doublePrecision("${name}")`;
1576
+ if (dt === "numeric" || dt === "decimal")
1577
+ return `numeric("${name}")`;
1578
+ if (dt === "json")
1579
+ return `json("${name}")`;
1580
+ if (dt === "jsonb")
1581
+ return `jsonb("${name}")`;
1582
+ if (dt === "timestamp without time zone")
1583
+ return `timestamp("${name}")`;
1584
+ if (dt === "timestamp with time zone")
1585
+ return `timestamptz("${name}")`;
1586
+ if (dt === "date")
1587
+ return `date("${name}")`;
1588
+ if (dt === "time without time zone")
1589
+ return `time("${name}")`;
1590
+ if (dt === "bytea")
1591
+ return `bytea("${name}")`;
1592
+ if (dt === "inet")
1593
+ return `inet("${name}")`;
1594
+ if (dt === "USER-DEFINED" && col2.udtName === "citext")
1595
+ return `text("${name}")`;
1596
+ return `text("${name}") /* original type: ${dt} */`;
1597
+ }
1598
+ function toCamelCase(str) {
1599
+ return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
1600
+ }
1601
+
1602
+ // src/commands/status.ts
1603
+ import { resolve as resolve6 } from "path";
1604
+ var MIGRATIONS_TABLE2 = "__bungres_migrations";
1605
+ async function runStatus(config2) {
1606
+ const migrationsDir = resolve6(config2.migrationsDir);
1607
+ const sql2 = new Bun.SQL(config2.dbUrl);
1608
+ try {
1609
+ const tableCheck = await sql2.unsafe(`SELECT EXISTS (
1610
+ SELECT 1 FROM information_schema.tables
1611
+ WHERE table_name = $1
1612
+ ) AS exists`, [MIGRATIONS_TABLE2]);
1613
+ const trackingExists = tableCheck[0]?.exists ?? false;
1614
+ const glob = new Bun.Glob("*.sql");
1615
+ const files = [];
1616
+ for await (const file of glob.scan({ cwd: migrationsDir, absolute: false })) {
1617
+ files.push(file);
1618
+ }
1619
+ files.sort();
1620
+ if (files.length === 0) {
1621
+ console.log(colorize("No migration files found. Run `bungres generate` first.", "yellow"));
1622
+ return;
1623
+ }
1624
+ let appliedSet = new Set;
1625
+ if (trackingExists) {
1626
+ const applied = await sql2.unsafe(`SELECT name FROM "${MIGRATIONS_TABLE2}" ORDER BY applied_at`);
1627
+ appliedSet = new Set(applied.map((r) => r.name));
1628
+ }
1629
+ console.log(colorize(`
1630
+ Migration status:
1631
+ `, "cyan"));
1632
+ let pendingCount = 0;
1633
+ for (const file of files) {
1634
+ const isApplied = appliedSet.has(file);
1635
+ const status = isApplied ? colorize("\u2713 applied ", "green") : colorize("\u2717 pending ", "yellow");
1636
+ if (!isApplied)
1637
+ pendingCount++;
1638
+ console.log(` ${status} ${file}`);
1639
+ }
1640
+ console.log(`
1641
+ ${colorize(appliedSet.size.toString(), "green")} applied, ${colorize(pendingCount.toString(), "yellow")} pending.
1642
+ `);
1643
+ } finally {
1644
+ await sql2.end();
1645
+ }
1646
+ }
1647
+
1648
+ // src/commands/drop.ts
1649
+ async function runDrop(config2, opts = {}) {
1650
+ const schemas2 = await loadSchemas(config2.schema);
1651
+ if (schemas2.length === 0) {
1652
+ console.warn("No table definitions found in schema files.");
1653
+ return;
1654
+ }
1655
+ const sql2 = new Bun.SQL(config2.dbUrl);
1656
+ try {
1657
+ const existingTablesResult = await sql2`
1658
+ SELECT table_name
1659
+ FROM information_schema.tables
1660
+ WHERE table_schema = 'public' OR table_schema = current_schema()
1661
+ `;
1662
+ const existingTableNames = new Set(existingTablesResult.map((row) => row.table_name));
1663
+ const migrationTableExists = existingTableNames.has("__bungres_migrations");
1664
+ const pushTableExists = existingTableNames.has("__bungres_push");
1665
+ const tablesToDrop = schemas2.filter((s) => existingTableNames.has(s.config.name));
1666
+ if (tablesToDrop.length === 0 && !migrationTableExists && !pushTableExists) {
1667
+ console.log("No tables to drop (they either don't exist or were already dropped).");
1668
+ return;
1669
+ }
1670
+ const tableNamesToPrint = tablesToDrop.map((s) => (s.config.schema ? s.config.schema + "." : "") + s.config.name);
1671
+ if (migrationTableExists) {
1672
+ tableNamesToPrint.push("__bungres_migrations");
1673
+ }
1674
+ if (pushTableExists) {
1675
+ tableNamesToPrint.push("__bungres_push");
1676
+ }
1677
+ if (!opts.force) {
1678
+ console.warn(colorize(`
1679
+ \u26A0\uFE0F WARNING: This will drop the following tables and ALL their data:
1680
+ `, "red"));
1681
+ for (const name of tableNamesToPrint)
1682
+ console.log(colorize(` - ${name}`, "yellow"));
1683
+ process.stdout.write(colorize(`
1684
+ Are you sure? Type YES to continue: `, "cyan"));
1685
+ for await (const line2 of console) {
1686
+ if (line2.trim().toLowerCase() !== "yes") {
1687
+ console.log("Aborted.");
1688
+ return;
1689
+ }
1690
+ break;
1691
+ }
1692
+ }
1693
+ for (const entry of tablesToDrop) {
1694
+ const ddl = `DROP TABLE IF EXISTS "${entry.config.name}" CASCADE;`;
1695
+ await sql2.unsafe(ddl);
1696
+ console.log(colorize(` \u2713 dropped ${entry.config.name}`, "green"));
1697
+ }
1698
+ if (migrationTableExists) {
1699
+ await sql2.unsafe("DROP TABLE IF EXISTS public.__bungres_migrations CASCADE");
1700
+ console.log(colorize(` \u2713 dropped __bungres_migrations`, "green"));
1701
+ }
1702
+ if (pushTableExists) {
1703
+ await sql2.unsafe("DROP TABLE IF EXISTS public.__bungres_push CASCADE");
1704
+ console.log(colorize(` \u2713 dropped __bungres_push`, "green"));
1705
+ }
1706
+ console.log(colorize(`
1707
+ Drop complete.`, "green"));
1708
+ } finally {
1709
+ await sql2.end();
1710
+ }
1711
+ }
1712
+
1713
+ // src/commands/fresh.ts
1714
+ async function runFresh(config2) {
1715
+ console.log("Dropping all tables...");
1716
+ await runDrop(config2, { force: true });
1717
+ console.log(`
1718
+ Re-running migrations...`);
1719
+ await runMigrate(config2);
1720
+ console.log(`
1721
+ Fresh complete.`);
1722
+ }
1723
+
1724
+ // src/commands/refresh.ts
1725
+ async function runRefresh(config2) {
1726
+ const schemas2 = await loadSchemas(config2.schema);
1727
+ if (schemas2.length === 0) {
1728
+ console.warn("No table definitions found in schema files.");
1729
+ return;
1730
+ }
1731
+ const sql2 = new Bun.SQL(config2.dbUrl);
1732
+ try {
1733
+ const tableNames = schemas2.map((s) => `"${s.config.schema ? s.config.schema + '"."' : ""}${s.config.name}"`);
1734
+ console.log(`Truncating ${tableNames.length} tables...`);
1735
+ for (const entry of schemas2) {
1736
+ const ddl = `TRUNCATE TABLE "${entry.config.name}" CASCADE;`;
1737
+ await sql2.unsafe(ddl);
1738
+ console.log(colorize(` \u2713 truncated ${entry.config.name}`, "green"));
1739
+ }
1740
+ console.log(colorize(`
1741
+ Refresh complete. All tables are now empty.`, "green"));
1742
+ } finally {
1743
+ await sql2.end();
1744
+ }
1745
+ }
1746
+
1747
+ // src/commands/seed.ts
1748
+ import { resolve as resolve7 } from "path";
1749
+ async function runSeed(config2) {
1750
+ if (!config2.seed) {
1751
+ console.log(colorize(`No seed file configured in bungres.config.ts`, "yellow"));
1752
+ return;
1753
+ }
1754
+ const seedPath = resolve7(process.cwd(), config2.seed);
1755
+ const file = Bun.file(seedPath);
1756
+ if (!await file.exists()) {
1757
+ console.error(colorize(`Seed file not found at ${seedPath}`, "red"));
1758
+ process.exit(1);
1759
+ }
1760
+ console.log(colorize(`
1761
+ Running seeder: ${config2.seed}...`, "cyan"));
1762
+ const proc = Bun.spawn(["bun", "run", seedPath], {
1763
+ cwd: process.cwd(),
1764
+ stdout: "inherit",
1765
+ stderr: "inherit",
1766
+ env: { ...process.env, DATABASE_URL: config2.dbUrl }
1767
+ });
1768
+ const exitCode = await proc.exited;
1769
+ if (exitCode === 0) {
1770
+ console.log(`
1771
+ Seed complete.`);
1772
+ } else {
1773
+ console.error(`
1774
+ Seed failed with exit code ${exitCode}.`);
1775
+ process.exit(exitCode);
1776
+ }
1777
+ }
1778
+
1779
+ // src/commands/studio.ts
1780
+ async function runStudio(config2) {
1781
+ const schemas2 = await loadSchemas(config2.schema);
1782
+ if (schemas2.length === 0) {
1783
+ console.warn("No table definitions found in schema files.");
1784
+ return;
1785
+ }
1786
+ const schemaObj2 = {};
1787
+ for (const s of schemas2) {
1788
+ schemaObj2[s.exportName] = s.table;
1789
+ }
1790
+ const db2 = createDB({ url: config2.dbUrl, schema: schemaObj2 });
1791
+ const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 5555;
1792
+ const server = Bun.serve({
1793
+ port,
1794
+ async fetch(req) {
1795
+ const url = new URL(req.url);
1796
+ if (req.method === "GET" && url.pathname === "/api/tables") {
1797
+ const tables = schemas2.map((s) => ({
1798
+ name: s.config.name,
1799
+ exportName: s.exportName,
1800
+ columns: Object.entries(s.config.columns).map(([key, col2]) => ({
1801
+ name: col2.name,
1802
+ type: col2.dataType,
1803
+ primaryKey: col2.primaryKey
1804
+ }))
1805
+ }));
1806
+ return new Response(JSON.stringify(tables), {
1807
+ headers: { "Content-Type": "application/json" }
1808
+ });
1809
+ }
1810
+ if (req.method === "GET" && url.pathname.startsWith("/api/tables/") && url.pathname.endsWith("/data")) {
1811
+ const tableName = url.pathname.split("/")[3];
1812
+ const schema = schemas2.find((s) => s.config.name === tableName);
1813
+ if (!schema) {
1814
+ return new Response("Table not found", { status: 404 });
1815
+ }
1816
+ try {
1817
+ const data = await db2.select().from(schema.table).limit(100);
1818
+ return new Response(JSON.stringify(data), {
1819
+ headers: { "Content-Type": "application/json" }
1820
+ });
1821
+ } catch (e) {
1822
+ return new Response(JSON.stringify({ error: e.message }), {
1823
+ status: 500,
1824
+ headers: { "Content-Type": "application/json" }
1825
+ });
1826
+ }
1827
+ }
1828
+ if (req.method === "POST" && url.pathname === "/api/editor/open") {
1829
+ try {
1830
+ const body = await req.json();
1831
+ const tableName = body.tableName;
1832
+ const schema = schemas2.find((s) => s.config.name === tableName);
1833
+ if (schema && schema.filePath) {
1834
+ console.log(`Attempting to open ${schema.filePath} in editor...`);
1835
+ Bun.openInEditor(schema.filePath, { editor: "vscode" });
1836
+ return new Response(JSON.stringify({
1837
+ success: true,
1838
+ filePath: schema.filePath
1839
+ }), {
1840
+ headers: { "Content-Type": "application/json" }
1841
+ });
1842
+ }
1843
+ return new Response("Schema not found", { status: 404 });
1844
+ } catch (e) {
1845
+ return new Response("Invalid request", { status: 400 });
1846
+ }
1847
+ }
1848
+ if (req.method === "GET" && url.pathname === "/") {
1849
+ return new Response(htmlTemplate, {
1850
+ headers: { "Content-Type": "text/html" }
1851
+ });
1852
+ }
1853
+ return new Response("Not found", { status: 404 });
1854
+ }
1855
+ });
1856
+ console.log(colorize("=========================================", "cyan"));
1857
+ console.log(colorize(`\uD83D\uDC18 Bungres Studio is running!`, "cyan"));
1858
+ console.log(colorize(` Local: http://localhost:${server.port}`, "green"));
1859
+ console.log(colorize("=========================================", "cyan"));
1860
+ }
1861
+ var htmlTemplate = `
1862
+ <!DOCTYPE html>
1863
+ <html lang="en">
1864
+ <head>
1865
+ <meta charset="UTF-8">
1866
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1867
+ <title>Bungres Studio</title>
1868
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
1869
+ <style>
1870
+ :root {
1871
+ --bg-color: #0d1117;
1872
+ --panel-bg: #161b22;
1873
+ --border-color: #30363d;
1874
+ --text-main: #c9d1d9;
1875
+ --text-muted: #8b949e;
1876
+ --accent-color: #58a6ff;
1877
+ --accent-hover: #1f6feb;
1878
+ --header-bg: #161b22;
1879
+ --row-hover: #1f2428;
1880
+ }
1881
+
1882
+ * { box-sizing: border-box; }
1883
+
1884
+ body {
1885
+ margin: 0;
1886
+ padding: 0;
1887
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
1888
+ background-color: var(--bg-color);
1889
+ color: var(--text-main);
1890
+ display: flex;
1891
+ height: 100vh;
1892
+ overflow: hidden;
1893
+ }
1894
+
1895
+ /* Sidebar */
1896
+ .sidebar {
1897
+ width: 250px;
1898
+ background-color: var(--panel-bg);
1899
+ border-right: 1px solid var(--border-color);
1900
+ display: flex;
1901
+ flex-direction: column;
1902
+ }
1903
+
1904
+ .sidebar-header {
1905
+ padding: 20px;
1906
+ border-bottom: 1px solid var(--border-color);
1907
+ display: flex;
1908
+ align-items: center;
1909
+ gap: 10px;
1910
+ }
1911
+
1912
+ .sidebar-header h1 {
1913
+ margin: 0;
1914
+ font-size: 16px;
1915
+ font-weight: 600;
1916
+ color: white;
1917
+ }
1918
+
1919
+ .table-list {
1920
+ flex: 1;
1921
+ overflow-y: auto;
1922
+ padding: 10px 0;
1923
+ list-style: none;
1924
+ margin: 0;
1925
+ }
1926
+
1927
+ .table-item {
1928
+ padding: 8px 20px;
1929
+ cursor: pointer;
1930
+ font-size: 14px;
1931
+ color: var(--text-muted);
1932
+ transition: all 0.2s ease;
1933
+ display: flex;
1934
+ align-items: center;
1935
+ gap: 8px;
1936
+ }
1937
+
1938
+ .table-item:hover {
1939
+ background-color: var(--row-hover);
1940
+ color: var(--text-main);
1941
+ }
1942
+
1943
+ .table-item.active {
1944
+ background-color: rgba(88, 166, 255, 0.1);
1945
+ color: var(--accent-color);
1946
+ border-right: 3px solid var(--accent-color);
1947
+ }
1948
+
1949
+ /* Main Content */
1950
+ .main {
1951
+ flex: 1;
1952
+ display: flex;
1953
+ flex-direction: column;
1954
+ background-color: var(--bg-color);
1955
+ }
1956
+
1957
+ .main-header {
1958
+ height: 60px;
1959
+ padding: 0 20px;
1960
+ border-bottom: 1px solid var(--border-color);
1961
+ display: flex;
1962
+ align-items: center;
1963
+ justify-content: space-between;
1964
+ background-color: var(--header-bg);
1965
+ }
1966
+
1967
+ .main-header h2 {
1968
+ margin: 0;
1969
+ font-size: 16px;
1970
+ font-weight: 500;
1971
+ color: white;
1972
+ }
1973
+
1974
+ .btn {
1975
+ background-color: var(--accent-color);
1976
+ color: white;
1977
+ border: none;
1978
+ padding: 6px 12px;
1979
+ border-radius: 6px;
1980
+ font-size: 12px;
1981
+ font-weight: 500;
1982
+ cursor: pointer;
1983
+ transition: background-color 0.2s ease;
1984
+ display: flex;
1985
+ align-items: center;
1986
+ gap: 6px;
1987
+ }
1988
+
1989
+ .btn:hover {
1990
+ background-color: var(--accent-hover);
1991
+ }
1992
+
1993
+ .btn:disabled {
1994
+ opacity: 0.5;
1995
+ cursor: not-allowed;
1996
+ }
1997
+
1998
+ .content-area {
1999
+ flex: 1;
2000
+ overflow: auto;
2001
+ padding: 20px;
2002
+ }
2003
+
2004
+ /* Data Table */
2005
+ .data-grid-container {
2006
+ background-color: var(--panel-bg);
2007
+ border: 1px solid var(--border-color);
2008
+ border-radius: 8px;
2009
+ overflow: hidden;
2010
+ }
2011
+
2012
+ table {
2013
+ width: 100%;
2014
+ border-collapse: collapse;
2015
+ text-align: left;
2016
+ font-size: 13px;
2017
+ }
2018
+
2019
+ th {
2020
+ background-color: rgba(255,255,255,0.02);
2021
+ color: var(--text-muted);
2022
+ font-weight: 500;
2023
+ padding: 10px 16px;
2024
+ border-bottom: 1px solid var(--border-color);
2025
+ white-space: nowrap;
2026
+ position: sticky;
2027
+ top: 0;
2028
+ z-index: 10;
2029
+ }
2030
+
2031
+ td {
2032
+ padding: 10px 16px;
2033
+ border-bottom: 1px solid var(--border-color);
2034
+ color: var(--text-main);
2035
+ max-width: 300px;
2036
+ overflow: hidden;
2037
+ text-overflow: ellipsis;
2038
+ white-space: nowrap;
2039
+ }
2040
+
2041
+ tr:last-child td {
2042
+ border-bottom: none;
2043
+ }
2044
+
2045
+ tr:hover td {
2046
+ background-color: var(--row-hover);
2047
+ }
2048
+
2049
+ .type-number { color: #79c0ff; }
2050
+ .type-string { color: #a5d6ff; }
2051
+ .type-boolean { color: #ff7b72; }
2052
+ .type-date { color: #d2a8ff; }
2053
+ .type-null { color: var(--text-muted); font-style: italic; }
2054
+
2055
+ .empty-state {
2056
+ display: flex;
2057
+ flex-direction: column;
2058
+ align-items: center;
2059
+ justify-content: center;
2060
+ height: 100%;
2061
+ color: var(--text-muted);
2062
+ }
2063
+ </style>
2064
+ </head>
2065
+ <body>
2066
+
2067
+ <div class="sidebar">
2068
+ <div class="sidebar-header">
2069
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2070
+ <ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
2071
+ <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
2072
+ <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
2073
+ </svg>
2074
+ <h1>Bungres Studio</h1>
2075
+ </div>
2076
+ <ul class="table-list" id="table-list">
2077
+ <!-- Populated by JS -->
2078
+ </ul>
2079
+ </div>
2080
+
2081
+ <div class="main">
2082
+ <div class="main-header">
2083
+ <h2 id="current-table-name">Select a table</h2>
2084
+ <button id="refresh-btn" class="btn" disabled>
2085
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2086
+ <polyline points="23 4 23 10 17 10"></polyline>
2087
+ <polyline points="1 20 1 14 7 14"></polyline>
2088
+ <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
2089
+ </svg>
2090
+ Refresh Data
2091
+ </button>
2092
+ </div>
2093
+ <div class="content-area">
2094
+ <div id="data-container" class="empty-state">
2095
+ <p>Select a table from the sidebar to view data</p>
2096
+ </div>
2097
+ </div>
2098
+ </div>
2099
+
2100
+ <script>
2101
+ let currentTable = null;
2102
+ let tablesData = [];
2103
+
2104
+ // Format values for the data grid
2105
+ function formatValue(val) {
2106
+ if (val === null || val === undefined) return '<span class="type-null">null</span>';
2107
+ if (typeof val === 'number') return \`<span class="type-number">\${val}</span>\`;
2108
+ if (typeof val === 'boolean') return \`<span class="type-boolean">\${val}</span>\`;
2109
+ if (val instanceof Date || (typeof val === 'string' && val.match(/^\\d{4}-\\d{2}-\\d{2}T/))) {
2110
+ return \`<span class="type-date">\${new Date(val).toLocaleString()}</span>\`;
2111
+ }
2112
+ // Escape HTML to prevent XSS
2113
+ const safeStr = String(val)
2114
+ .replace(/&/g, "&amp;")
2115
+ .replace(/</g, "&lt;")
2116
+ .replace(/>/g, "&gt;");
2117
+ return \`<span class="type-string">\${safeStr}</span>\`;
2118
+ }
2119
+
2120
+ async function loadTables() {
2121
+ try {
2122
+ const res = await fetch('/api/tables');
2123
+ tablesData = await res.json();
2124
+
2125
+ const list = document.getElementById('table-list');
2126
+ list.innerHTML = '';
2127
+
2128
+ tablesData.forEach(t => {
2129
+ const li = document.createElement('li');
2130
+ li.className = 'table-item';
2131
+ li.innerHTML = \`
2132
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2133
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
2134
+ <line x1="3" y1="9" x2="21" y2="9"></line>
2135
+ <line x1="9" y1="21" x2="9" y2="9"></line>
2136
+ </svg>
2137
+ \${t.name}
2138
+ \`;
2139
+ li.onclick = () => selectTable(t.name);
2140
+ list.appendChild(li);
2141
+ });
2142
+ } catch (e) {
2143
+ console.error("Failed to load tables", e);
2144
+ }
2145
+ }
2146
+
2147
+ async function selectTable(name) {
2148
+ currentTable = name;
2149
+
2150
+ // Update UI active state
2151
+ document.querySelectorAll('.table-item').forEach(el => {
2152
+ if (el.textContent.trim() === name) el.classList.add('active');
2153
+ else el.classList.remove('active');
2154
+ });
2155
+
2156
+ document.getElementById('current-table-name').textContent = name;
2157
+ document.getElementById('refresh-btn').disabled = false;
2158
+
2159
+ const container = document.getElementById('data-container');
2160
+ container.innerHTML = '<div class="empty-state">Loading data...</div>';
2161
+ container.className = '';
2162
+
2163
+ try {
2164
+ const res = await fetch(\`/api/tables/\${name}/data\`);
2165
+ if (!res.ok) throw new Error(await res.text());
2166
+ const rows = await res.json();
2167
+
2168
+ if (rows.length === 0) {
2169
+ container.innerHTML = '<div class="empty-state">No data in this table</div>';
2170
+ return;
2171
+ }
2172
+
2173
+ const columns = Object.keys(rows[0]);
2174
+
2175
+ let html = '<div class="data-grid-container"><table><thead><tr>';
2176
+ columns.forEach(col => {
2177
+ html += \`<th>\${col}</th>\`;
2178
+ });
2179
+ html += '</tr></thead><tbody>';
2180
+
2181
+ rows.forEach(row => {
2182
+ html += '<tr>';
2183
+ columns.forEach(col => {
2184
+ html += \`<td>\${formatValue(row[col])}</td>\`;
2185
+ });
2186
+ html += '</tr>';
2187
+ });
2188
+
2189
+ html += '</tbody></table></div>';
2190
+ container.innerHTML = html;
2191
+
2192
+ } catch (e) {
2193
+ container.innerHTML = \`<div class="empty-state" style="color: #ff7b72;">Error: \${e.message}</div>\`;
2194
+ }
2195
+ }
2196
+
2197
+ document.getElementById('refresh-btn').addEventListener('click', async () => {
2198
+ if (!currentTable) return;
2199
+
2200
+ const btn = document.getElementById('refresh-btn');
2201
+ const originalText = btn.innerHTML;
2202
+ btn.innerHTML = 'Refreshing...';
2203
+
2204
+ try {
2205
+ await selectTable(currentTable);
2206
+ } catch (e) {
2207
+ console.error("Failed to refresh data", e);
2208
+ } finally {
2209
+ setTimeout(() => { btn.innerHTML = originalText; }, 300);
2210
+ }
2211
+ });
2212
+
2213
+ // Init
2214
+ loadTables();
2215
+ </script>
2216
+ </body>
2217
+ </html>
2218
+ `;
2219
+
2220
+ // src/commands/tusky.ts
2221
+ import * as readline from "readline";
2222
+ async function runTusky(config) {
2223
+ const schemas = await loadSchemas(config.schema);
2224
+ const schemaObj = {};
2225
+ for (const s of schemas) {
2226
+ schemaObj[s.exportName] = s.table;
2227
+ }
2228
+ const db = createDB({ url: config.dbUrl, schema: schemaObj });
2229
+ console.log("=========================================");
2230
+ console.log("\uD83D\uDC18 Welcome to Bungres REPL (Tusky)");
2231
+ console.log("=========================================");
2232
+ console.log(`
2233
+ Database connection established.`);
2234
+ console.log(`
2235
+ Pre-loaded Context:`);
2236
+ console.log(" - db (Bungres Database Client)");
2237
+ for (const s of schemas) {
2238
+ console.log(` - ${s.exportName} (Table)`);
2239
+ }
2240
+ console.log(`
2241
+ Example query: await db.select().from(users)`);
2242
+ console.log(`Type .exit to quit.
2243
+ `);
2244
+ const rl = readline.createInterface({
2245
+ input: process.stdin,
2246
+ output: process.stdout,
2247
+ prompt: "bungres> "
2248
+ });
2249
+ globalThis.db = db;
2250
+ for (const s of schemas) {
2251
+ globalThis[s.exportName] = s.table;
2252
+ }
2253
+ rl.prompt();
2254
+ rl.on("line", async (line) => {
2255
+ const input = line.trim();
2256
+ if (input === ".exit") {
2257
+ rl.close();
2258
+ return;
2259
+ }
2260
+ if (!input) {
2261
+ rl.prompt();
2262
+ return;
2263
+ }
2264
+ try {
2265
+ let code = input;
2266
+ const isRawSql = /^(select|insert|update|delete|create|drop|alter|truncate|with)\b/i.test(input);
2267
+ if (isRawSql) {
2268
+ console.log(`(Running as raw SQL: await db.raw(\`${input}\`))`);
2269
+ code = `(async () => { return await db.raw(\`${input}\`); })()`;
2270
+ } else if (input.includes("await ")) {
2271
+ code = `(async () => { return ${input}; })()`;
2272
+ }
2273
+ const result = await eval(code);
2274
+ console.log(result);
2275
+ } catch (err) {
2276
+ console.error(err.message || err);
2277
+ }
2278
+ rl.prompt();
2279
+ });
2280
+ rl.on("close", () => {
2281
+ process.exit(0);
2282
+ });
2283
+ }
2284
+
2285
+ // src/ensure-db.ts
2286
+ async function ensureDatabase2(dbUrl) {
2287
+ let sql2;
2288
+ try {
2289
+ sql2 = new Bun.SQL(dbUrl);
2290
+ await sql2.unsafe("SELECT 1");
2291
+ return;
2292
+ } catch (err) {
2293
+ if (err.message && err.message.includes("does not exist")) {
2294
+ try {
2295
+ const parsed = new URL(dbUrl);
2296
+ const dbName = parsed.pathname.slice(1);
2297
+ parsed.pathname = "/postgres";
2298
+ const fallbackUrl = parsed.toString();
2299
+ const fallback = new Bun.SQL(fallbackUrl);
2300
+ try {
2301
+ await fallback.unsafe(`CREATE DATABASE "${dbName}"`);
2302
+ console.log(`Created database "${dbName}" automatically.`);
2303
+ } finally {
2304
+ await fallback.end();
2305
+ }
2306
+ } catch (e) {}
2307
+ }
2308
+ } finally {
2309
+ if (sql2) {
2310
+ await sql2.end();
2311
+ }
2312
+ }
2313
+ }
2314
+
2315
+ // src/cli.ts
2316
+ var VERSION = "0.0.1";
2317
+ var HELP = `
2318
+ ${colorize("\uD83D\uDC18 Bungres ORM CLI", "cyan")} v${VERSION}
2319
+
2320
+ ${colorize("Usage:", "yellow")}
2321
+ bungres <command> [options]
2322
+
2323
+ ${colorize("Commands:", "yellow")}
2324
+ generate Generate SQL migration files from your schema definitions
2325
+ migrate Run pending migration files against the database
2326
+ push Apply schema directly to the DB (no migration files, dev mode)
2327
+ pull Introspect the database and generate TypeScript schema
2328
+ status Show applied vs. pending migrations
2329
+ fresh Drop all tables and re-run all migrations from scratch
2330
+ refresh Truncate all tables to quickly reset data without dropping schema
2331
+ seed Execute the seed script to populate the database
2332
+ studio Start a local web interface to browse database data
2333
+ tusky Boot up a Node REPL connected to the database with schema loaded
2334
+ drop Drop all tables defined in the schema (dev only)
2335
+
2336
+ ${colorize("Options:", "yellow")}
2337
+ --config Path to config file (default: bungres.config.ts)
2338
+ --verbose Enable verbose SQL logging
2339
+ --force Skip confirmation prompts (use with drop)
2340
+ --version Show version
2341
+ --help Show this help
2342
+
2343
+ ${colorize("Examples:", "yellow")}
2344
+ bungres generate
2345
+ bungres migrate
2346
+ bungres push
2347
+ bungres pull
2348
+ bungres status
2349
+ bungres fresh
2350
+ bungres refresh
2351
+ bungres seed
2352
+ bungres studio
2353
+ bungres tusky
2354
+ bungres drop --force
2355
+ `.trim();
2356
+ async function main() {
2357
+ const args = process.argv.slice(2);
2358
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
2359
+ console.log(HELP);
2360
+ process.exit(0);
2361
+ }
2362
+ if (args.includes("--version") || args.includes("-v")) {
2363
+ console.log(`@bungres/kit v${VERSION}`);
2364
+ process.exit(0);
2365
+ }
2366
+ const command = args[0];
2367
+ const flags = parseFlags(args.slice(1));
2368
+ const config2 = await loadConfig(process.cwd());
2369
+ if (flags.verbose)
2370
+ config2.verbose = true;
2371
+ if (command && ["migrate", "push", "drop", "status", "fresh", "refresh"].includes(command)) {
2372
+ await ensureDatabase2(config2.dbUrl);
2373
+ }
2374
+ switch (command) {
2375
+ case "generate":
2376
+ await runGenerate(config2, flags.name);
2377
+ break;
2378
+ case "migrate":
2379
+ await runMigrate(config2);
2380
+ break;
2381
+ case "push":
2382
+ await runPush(config2, { force: !!flags.force });
2383
+ break;
2384
+ case "pull":
2385
+ await runPull(config2);
2386
+ break;
2387
+ case "status":
2388
+ await runStatus(config2);
2389
+ break;
2390
+ case "drop":
2391
+ await runDrop(config2, { force: !!flags.force });
2392
+ break;
2393
+ case "fresh":
2394
+ await runFresh(config2);
2395
+ break;
2396
+ case "refresh":
2397
+ await runRefresh(config2);
2398
+ break;
2399
+ case "seed":
2400
+ await runSeed(config2);
2401
+ break;
2402
+ case "studio":
2403
+ await runStudio(config2);
2404
+ break;
2405
+ case "tusky":
2406
+ await runTusky(config2);
2407
+ break;
2408
+ default:
2409
+ console.error(`Unknown command: ${command}
2410
+ `);
2411
+ console.log(HELP);
2412
+ process.exit(1);
2413
+ }
2414
+ }
2415
+ function parseFlags(args) {
2416
+ const flags = {};
2417
+ for (let i = 0;i < args.length; i++) {
2418
+ const arg = args[i];
2419
+ if (arg?.startsWith("--")) {
2420
+ const key = arg.slice(2);
2421
+ const next = args[i + 1];
2422
+ if (next && !next.startsWith("--")) {
2423
+ flags[key] = next;
2424
+ i++;
2425
+ } else {
2426
+ flags[key] = true;
2427
+ }
2428
+ }
2429
+ }
2430
+ return flags;
2431
+ }
2432
+ main().catch((err) => {
2433
+ console.error("@bungres/kit error:", err instanceof Error ? err.message : err);
2434
+ process.exit(1);
2435
+ });