@bungres/kit 0.3.0 → 0.5.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/dist/cli.js CHANGED
@@ -1,47 +1,10 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
3
 
4
- // src/config.ts
4
+ // src/schema-loader.ts
5
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
6
 
41
- // src/commands/push.ts
42
- import * as fs from "fs";
43
-
44
- // ../../node_modules/.bun/@bungres+orm@0.2.0/node_modules/@bungres/orm/src/table.ts
7
+ // ../bungres-orm/src/schema/table.ts
45
8
  var TableConfigSymbol = Symbol.for("BungresTableConfig");
46
9
  function getTableConfig(table) {
47
10
  return table[TableConfigSymbol];
@@ -51,8 +14,13 @@ function camelToSnakeCase(str) {
51
14
  }
52
15
  function createTableFactory(casing) {
53
16
  return function(name, columns, extra) {
17
+ let schema;
18
+ if (extra && typeof extra !== "function") {
19
+ schema = extra.schema;
20
+ }
21
+ const qualifiedName = schema ? `"${schema}"."${name}"` : `"${name}"`;
54
22
  const columnConfigs = Object.fromEntries(Object.entries(columns).map(([key, config2]) => {
55
- const c = { ...config2 };
23
+ const c = { ...config2, tableName: qualifiedName };
56
24
  if (!c.name) {
57
25
  if (casing === "snake") {
58
26
  c.name = camelToSnakeCase(key);
@@ -62,26 +30,50 @@ function createTableFactory(casing) {
62
30
  }
63
31
  return [key, c];
64
32
  }));
65
- const schema = extra?.schema;
66
- const qualifiedName = schema ? `"${schema}"."${name}"` : `"${name}"`;
33
+ const indexes = [];
34
+ const checks = [];
35
+ const primaryKeys = [];
36
+ const foreignKeys = [];
37
+ if (extra) {
38
+ if (typeof extra === "function") {
39
+ const builders = extra(columnConfigs);
40
+ for (const builder of builders) {
41
+ const config2 = builder.build();
42
+ if (config2.type === "index")
43
+ indexes.push(config2);
44
+ else if (config2.type === "check")
45
+ checks.push(config2.condition);
46
+ else if (config2.type === "primaryKey")
47
+ primaryKeys.push(...config2.columns);
48
+ else if (config2.type === "foreignKey")
49
+ foreignKeys.push(config2);
50
+ }
51
+ } else {
52
+ if (extra.indexes)
53
+ indexes.push(...extra.indexes);
54
+ if (extra.checks)
55
+ checks.push(...extra.checks);
56
+ }
57
+ }
67
58
  const tableObj = {
68
59
  [TableConfigSymbol]: {
69
60
  name,
70
61
  schema,
71
62
  columns: columnConfigs,
72
- indexes: extra?.indexes ?? [],
73
- checks: extra?.checks ?? [],
74
- primaryKeys: extra?.primaryKeys ?? [],
63
+ indexes,
64
+ checks,
65
+ primaryKeys,
66
+ foreignKeys,
75
67
  qualifiedName
76
68
  }
77
69
  };
78
70
  return Object.assign(tableObj, columnConfigs);
79
71
  };
80
72
  }
81
- var table = createTableFactory("none");
73
+ var table = createTableFactory("snake");
82
74
  var snakeCase = { table: createTableFactory("snake") };
83
75
  var camelCase = { table: createTableFactory("camel") };
84
- // ../../node_modules/.bun/@bungres+orm@0.2.0/node_modules/@bungres/orm/src/column.ts
76
+ // ../bungres-orm/src/schema/columns.ts
85
77
  function buildColumn(dataType, nameOrOpts, opts) {
86
78
  let name = "";
87
79
  let options = opts;
@@ -135,7 +127,33 @@ var interval = col("interval");
135
127
  var inet = col("inet");
136
128
  var cidr = col("cidr");
137
129
  var macaddr = col("macaddr");
138
- // ../../node_modules/.bun/@bungres+orm@0.2.0/node_modules/@bungres/orm/src/sql.ts
130
+ var textArray = col("text[]");
131
+ var integerArray = col("integer[]");
132
+ var varcharArray = col("varchar[]");
133
+ var uuidArray = col("uuid[]");
134
+ // ../bungres-orm/src/core/sql.ts
135
+ function sql(strings, ...values) {
136
+ let query = "";
137
+ const params = [];
138
+ for (let i = 0;i < strings.length; i++) {
139
+ query += strings[i];
140
+ if (i < values.length) {
141
+ const val = values[i];
142
+ if (isSQLChunk(val)) {
143
+ const offset = params.length;
144
+ query += val.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
145
+ params.push(...val.params);
146
+ } else {
147
+ params.push(val);
148
+ query += `$${params.length}`;
149
+ }
150
+ }
151
+ }
152
+ return { sql: query, params };
153
+ }
154
+ function isSQLChunk(value) {
155
+ return typeof value === "object" && value !== null && "sql" in value && "params" in value && typeof value.sql === "string" && Array.isArray(value.params);
156
+ }
139
157
  function sqlJoin(chunks, separator = ", ") {
140
158
  const params = [];
141
159
  const parts = [];
@@ -146,12 +164,326 @@ function sqlJoin(chunks, separator = ", ") {
146
164
  }
147
165
  return { sql: parts.join(separator), params };
148
166
  }
149
- // ../../node_modules/.bun/@bungres+orm@0.2.0/node_modules/@bungres/orm/src/query.ts
167
+ function rawSql(query) {
168
+ return { sql: query, params: [] };
169
+ }
170
+ // ../bungres-orm/src/core/conditions.ts
171
+ var colName = (c) => {
172
+ if (typeof c === "string")
173
+ return `"${c}"`;
174
+ if (isSQLChunk(c))
175
+ return c.sql;
176
+ return c.tableName ? `${c.tableName}."${c.name}"` : `"${c.name}"`;
177
+ };
178
+ function isColumnConfig(val) {
179
+ return val !== null && typeof val === "object" && "name" in val && "dataType" in val;
180
+ }
181
+ var eq = (column, value) => {
182
+ if (isColumnConfig(value))
183
+ return sql`${rawSql(colName(column))} = ${rawSql(colName(value))}`;
184
+ if (isSQLChunk(value))
185
+ return sql`${rawSql(colName(column))} = ${value}`;
186
+ return sql`${rawSql(colName(column))} = ${value}`;
187
+ };
188
+ var ne = (column, value) => {
189
+ if (isColumnConfig(value))
190
+ return sql`${rawSql(colName(column))} != ${rawSql(colName(value))}`;
191
+ if (isSQLChunk(value))
192
+ return sql`${rawSql(colName(column))} != ${value}`;
193
+ return sql`${rawSql(colName(column))} != ${value}`;
194
+ };
195
+ var gt = (column, value) => {
196
+ if (isColumnConfig(value))
197
+ return sql`${rawSql(colName(column))} > ${rawSql(colName(value))}`;
198
+ if (isSQLChunk(value))
199
+ return sql`${rawSql(colName(column))} > ${value}`;
200
+ return sql`${rawSql(colName(column))} > ${value}`;
201
+ };
202
+ var gte = (column, value) => {
203
+ if (isColumnConfig(value))
204
+ return sql`${rawSql(colName(column))} >= ${rawSql(colName(value))}`;
205
+ if (isSQLChunk(value))
206
+ return sql`${rawSql(colName(column))} >= ${value}`;
207
+ return sql`${rawSql(colName(column))} >= ${value}`;
208
+ };
209
+ var lt = (column, value) => {
210
+ if (isColumnConfig(value))
211
+ return sql`${rawSql(colName(column))} < ${rawSql(colName(value))}`;
212
+ if (isSQLChunk(value))
213
+ return sql`${rawSql(colName(column))} < ${value}`;
214
+ return sql`${rawSql(colName(column))} < ${value}`;
215
+ };
216
+ var lte = (column, value) => {
217
+ if (isColumnConfig(value))
218
+ return sql`${rawSql(colName(column))} <= ${rawSql(colName(value))}`;
219
+ if (isSQLChunk(value))
220
+ return sql`${rawSql(colName(column))} <= ${value}`;
221
+ return sql`${rawSql(colName(column))} <= ${value}`;
222
+ };
223
+ var like = (column, pattern) => sql`${rawSql(colName(column))} LIKE ${pattern}`;
224
+ var ilike = (column, pattern) => sql`${rawSql(colName(column))} ILIKE ${pattern}`;
225
+ var isNull = (column) => rawSql(`${colName(column)} IS NULL`);
226
+ var isNotNull = (column) => rawSql(`${colName(column)} IS NOT NULL`);
227
+ var inArray = (column, values) => {
228
+ if (values.length === 0)
229
+ return rawSql("FALSE");
230
+ const params = values;
231
+ const placeholders = params.map((_, i) => `$${i + 1}`).join(", ");
232
+ return { sql: `${colName(column)} = ANY(ARRAY[${placeholders}])`, params };
233
+ };
234
+ var and = (...conditions) => sqlJoin(conditions, " AND ");
235
+ var or = (...conditions) => {
236
+ const joined = sqlJoin(conditions, " OR ");
237
+ return { sql: `(${joined.sql})`, params: joined.params };
238
+ };
239
+ var not = (condition) => ({
240
+ sql: `NOT (${condition.sql})`,
241
+ params: condition.params
242
+ });
243
+ var asc = (column) => sql`${rawSql(colName(column))} ASC`;
244
+ var desc = (column) => sql`${rawSql(colName(column))} DESC`;
245
+ function parseWhereObject(tableConfig, whereObj) {
246
+ const conditions = [];
247
+ for (const [key, val] of Object.entries(whereObj)) {
248
+ if (val === undefined)
249
+ continue;
250
+ if (key === "OR") {
251
+ const orConditions = val.map((o) => parseWhereObject(tableConfig, o));
252
+ if (orConditions.length > 0)
253
+ conditions.push(or(...orConditions));
254
+ continue;
255
+ }
256
+ if (key === "AND") {
257
+ const andConditions = val.map((o) => parseWhereObject(tableConfig, o));
258
+ if (andConditions.length > 0)
259
+ conditions.push(and(...andConditions));
260
+ continue;
261
+ }
262
+ if (key === "NOT") {
263
+ conditions.push(not(parseWhereObject(tableConfig, val)));
264
+ continue;
265
+ }
266
+ const colConfig = tableConfig.columns[key];
267
+ const columnArg = colConfig ?? key;
268
+ if (val !== null && typeof val === "object" && !Array.isArray(val) && !(val instanceof Date) && !(val instanceof Uint8Array)) {
269
+ const opVal = val;
270
+ if (opVal.eq !== undefined)
271
+ conditions.push(eq(columnArg, opVal.eq));
272
+ if (opVal.ne !== undefined)
273
+ conditions.push(ne(columnArg, opVal.ne));
274
+ if (opVal.gt !== undefined)
275
+ conditions.push(gt(columnArg, opVal.gt));
276
+ if (opVal.gte !== undefined)
277
+ conditions.push(gte(columnArg, opVal.gte));
278
+ if (opVal.lt !== undefined)
279
+ conditions.push(lt(columnArg, opVal.lt));
280
+ if (opVal.lte !== undefined)
281
+ conditions.push(lte(columnArg, opVal.lte));
282
+ if (opVal.in !== undefined)
283
+ conditions.push(inArray(columnArg, opVal.in));
284
+ if (opVal.like !== undefined)
285
+ conditions.push(like(columnArg, opVal.like));
286
+ if (opVal.ilike !== undefined)
287
+ conditions.push(ilike(columnArg, opVal.ilike));
288
+ if (opVal.isNull)
289
+ conditions.push(isNull(columnArg));
290
+ if (opVal.isNotNull)
291
+ conditions.push(isNotNull(columnArg));
292
+ } else {
293
+ if (val === null) {
294
+ conditions.push(isNull(columnArg));
295
+ } else {
296
+ conditions.push(eq(columnArg, val));
297
+ }
298
+ }
299
+ }
300
+ if (conditions.length === 0) {
301
+ return rawSql("TRUE");
302
+ }
303
+ return and(...conditions);
304
+ }
305
+ function parseOrderByObject(tableConfig, orderByObj) {
306
+ const parts = [];
307
+ for (const [key, dir] of Object.entries(orderByObj)) {
308
+ if (dir === undefined)
309
+ continue;
310
+ const colConfig = tableConfig.columns[key];
311
+ const columnArg = colConfig ?? key;
312
+ if (dir === "asc")
313
+ parts.push(asc(columnArg));
314
+ else if (dir === "desc")
315
+ parts.push(desc(columnArg));
316
+ }
317
+ return parts;
318
+ }
319
+
320
+ // ../bungres-orm/src/builders/delete.ts
321
+ class DeleteBuilder {
322
+ _table;
323
+ _executor;
324
+ _where = [];
325
+ _returning;
326
+ _comment;
327
+ constructor(table2, executor) {
328
+ this._table = table2;
329
+ this._executor = executor;
330
+ }
331
+ then(onfulfilled, onrejected) {
332
+ return this._executor.execute(this).then(onfulfilled, onrejected);
333
+ }
334
+ async single() {
335
+ return this._executor.executeSingle(this);
336
+ }
337
+ where(condition) {
338
+ if (condition && typeof condition === "object" && !("sql" in condition)) {
339
+ this._where.push(parseWhereObject(getTableConfig(this._table), condition));
340
+ } else {
341
+ this._where.push(condition);
342
+ }
343
+ return this;
344
+ }
345
+ returning(...columns) {
346
+ this._returning = columns.length > 0 ? columns : ["*"];
347
+ return this;
348
+ }
349
+ comment(tag) {
350
+ this._comment = tag;
351
+ return this;
352
+ }
353
+ toSQL() {
354
+ let query = `DELETE FROM ${getTableConfig(this._table).qualifiedName}`;
355
+ const params = [];
356
+ if (this._where.length > 0) {
357
+ const combined = sqlJoin(this._where, " AND ");
358
+ query += " WHERE " + combined.sql;
359
+ params.push(...combined.params);
360
+ }
361
+ if (this._returning) {
362
+ 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(", "));
363
+ }
364
+ return { sql: query, params };
365
+ }
366
+ }
367
+ // ../bungres-orm/src/builders/insert.ts
368
+ class InsertBuilder {
369
+ _table;
370
+ _executor;
371
+ _values = [];
372
+ _onConflict;
373
+ _returning;
374
+ _comment;
375
+ constructor(table2, executor) {
376
+ this._table = table2;
377
+ this._executor = executor;
378
+ }
379
+ then(onfulfilled, onrejected) {
380
+ return this._executor.execute(this).then(onfulfilled, onrejected);
381
+ }
382
+ async single() {
383
+ return this._executor.executeSingle(this);
384
+ }
385
+ values(data) {
386
+ if (Array.isArray(data)) {
387
+ this._values.push(...data);
388
+ } else {
389
+ this._values.push(data);
390
+ }
391
+ return this;
392
+ }
393
+ onConflictDoNothing() {
394
+ this._onConflict = "do nothing";
395
+ return this;
396
+ }
397
+ onConflict(clause) {
398
+ this._onConflict = clause;
399
+ return this;
400
+ }
401
+ returning(...columns) {
402
+ this._returning = columns.length > 0 ? columns : ["*"];
403
+ return this;
404
+ }
405
+ comment(tag) {
406
+ this._comment = tag;
407
+ return this;
408
+ }
409
+ toSQL() {
410
+ if (this._values.length === 0) {
411
+ throw new Error("InsertBuilder: no values provided");
412
+ }
413
+ const tConfig = getTableConfig(this._table);
414
+ const keySet = new Set;
415
+ for (const v of this._values) {
416
+ for (const k of Object.keys(v)) {
417
+ keySet.add(k);
418
+ }
419
+ }
420
+ const keys = Array.from(keySet);
421
+ if (keys.length === 0) {
422
+ return { sql: `INSERT INTO ${tConfig.qualifiedName} DEFAULT VALUES`, params: [] };
423
+ }
424
+ const columnsStr = keys.map((k) => `"${tConfig.columns[k]?.name ?? k}"`).join(", ");
425
+ const params = [];
426
+ const valuesStrs = this._values.map((v) => {
427
+ const vals = keys.map((k) => {
428
+ const val = v[k];
429
+ if (val && typeof val === "object" && "sql" in val && "params" in val) {
430
+ const chunk = val;
431
+ const offset = params.length;
432
+ params.push(...chunk.params);
433
+ return chunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
434
+ }
435
+ if (val && typeof val === "object" && !(val instanceof Date)) {
436
+ const colType = tConfig.columns[k]?.dataType;
437
+ if (colType === "json" || colType === "jsonb") {
438
+ params.push(val);
439
+ } else if (Array.isArray(val)) {
440
+ const pgArray = "{" + val.map((item) => {
441
+ if (item === null || item === undefined)
442
+ return "NULL";
443
+ if (typeof item === "string")
444
+ return '"' + item.replace(/"/g, "\\\"") + '"';
445
+ return typeof item === "object" ? '"' + JSON.stringify(item).replace(/"/g, "\\\"") + '"' : String(item);
446
+ }).join(",") + "}";
447
+ params.push(pgArray);
448
+ } else {
449
+ params.push(JSON.stringify(val));
450
+ }
451
+ return `$${params.length}`;
452
+ }
453
+ if (val === undefined)
454
+ return "DEFAULT";
455
+ params.push(val);
456
+ return `$${params.length}`;
457
+ });
458
+ return `(${vals.join(", ")})`;
459
+ });
460
+ let query = `INSERT INTO ${tConfig.qualifiedName} (${columnsStr}) VALUES ${valuesStrs.join(", ")}`;
461
+ if (this._onConflict) {
462
+ if (this._onConflict === "do nothing") {
463
+ query += " ON CONFLICT DO NOTHING";
464
+ } else {
465
+ const offset = params.length;
466
+ query += " " + this._onConflict.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
467
+ params.push(...this._onConflict.params);
468
+ }
469
+ }
470
+ if (this._returning) {
471
+ query += " RETURNING " + (this._returning[0] === "*" ? Object.keys(tConfig.columns).map((c) => `"${tConfig.columns[c].name}" AS "${c}"`).join(", ") : this._returning.map((c) => `"${tConfig.columns[c]?.name ?? c}" AS "${c}"`).join(", "));
472
+ }
473
+ if (this._comment) {
474
+ query += ` /* ${this._comment} */`;
475
+ }
476
+ return { sql: query, params };
477
+ }
478
+ }
479
+ // ../bungres-orm/src/builders/select.ts
150
480
  class SelectBuilder {
151
481
  _table;
152
482
  _executor;
153
483
  _where = [];
154
484
  _orderBy = [];
485
+ _groupBy = [];
486
+ _having = [];
155
487
  _limit;
156
488
  _offset;
157
489
  _select;
@@ -178,11 +510,27 @@ class SelectBuilder {
178
510
  return this;
179
511
  }
180
512
  where(condition) {
181
- this._where.push(condition);
513
+ if (condition && typeof condition === "object" && !("sql" in condition)) {
514
+ this._where.push(parseWhereObject(getTableConfig(this._table), condition));
515
+ } else {
516
+ this._where.push(condition);
517
+ }
182
518
  return this;
183
519
  }
184
520
  orderBy(column, dir = "asc") {
185
- this._orderBy.push({ column: typeof column === "string" ? column : column.name, dir });
521
+ this._orderBy.push({ column, dir });
522
+ return this;
523
+ }
524
+ groupBy(...columns) {
525
+ this._groupBy.push(...columns);
526
+ return this;
527
+ }
528
+ having(condition) {
529
+ if (condition && typeof condition === "object" && !("sql" in condition)) {
530
+ this._having.push(parseWhereObject(getTableConfig(this._table), condition));
531
+ } else {
532
+ this._having.push(condition);
533
+ }
186
534
  return this;
187
535
  }
188
536
  limit(n) {
@@ -197,35 +545,118 @@ class SelectBuilder {
197
545
  this._joins.push(rawClause);
198
546
  return this;
199
547
  }
548
+ innerJoin(table2, condition) {
549
+ this._joins.push(sql`INNER JOIN ${rawSql(getTableConfig(table2).qualifiedName)} ON ${condition}`);
550
+ return this;
551
+ }
552
+ leftJoin(table2, condition) {
553
+ this._joins.push(sql`LEFT JOIN ${rawSql(getTableConfig(table2).qualifiedName)} ON ${condition}`);
554
+ return this;
555
+ }
556
+ rightJoin(table2, condition) {
557
+ this._joins.push(sql`RIGHT JOIN ${rawSql(getTableConfig(table2).qualifiedName)} ON ${condition}`);
558
+ return this;
559
+ }
560
+ fullJoin(table2, condition) {
561
+ this._joins.push(sql`FULL JOIN ${rawSql(getTableConfig(table2).qualifiedName)} ON ${condition}`);
562
+ return this;
563
+ }
564
+ _buildSelection(selection, params) {
565
+ const parts = [];
566
+ for (const [alias, col2] of Object.entries(selection)) {
567
+ if (typeof col2 === "object" && col2 !== null) {
568
+ if ("sql" in col2 && "params" in col2) {
569
+ const chunk = col2;
570
+ const offset = params.length;
571
+ params.push(...chunk.params);
572
+ parts.push(`'${alias}', ${chunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`)}`);
573
+ } else if ("name" in col2 && "dataType" in col2) {
574
+ const c = col2;
575
+ parts.push(`'${alias}', ${c.tableName ? c.tableName + "." : ""}"${c.name}"`);
576
+ } else {
577
+ parts.push(`'${alias}', json_build_object(${this._buildSelection(col2, params)})`);
578
+ }
579
+ }
580
+ }
581
+ return parts.join(", ");
582
+ }
200
583
  toSQL() {
201
584
  let cols = "";
585
+ const params = [];
202
586
  if (this._selection) {
203
- cols = Object.entries(this._selection).map(([alias, col2]) => `"${col2.name}" AS "${alias}"`).join(", ");
587
+ cols = Object.entries(this._selection).map(([alias, col2]) => {
588
+ if (typeof col2 === "object" && col2 !== null && "sql" in col2 && "params" in col2) {
589
+ const chunk = col2;
590
+ const offset = params.length;
591
+ params.push(...chunk.params);
592
+ return `${chunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`)} AS "${alias}"`;
593
+ } else if (typeof col2 === "object" && col2 !== null && "name" in col2 && "dataType" in col2) {
594
+ const c = col2;
595
+ return `${c.tableName ? c.tableName + "." : ""}"${c.name}" AS "${alias}"`;
596
+ } else {
597
+ return `json_build_object(${this._buildSelection(col2, params)}) AS "${alias}"`;
598
+ }
599
+ }).join(", ");
204
600
  } else if (this._select && this._select.length > 0) {
601
+ const qName = getTableConfig(this._table).qualifiedName;
205
602
  cols = this._select.map((c) => {
206
603
  if (typeof c === "string") {
207
- return `"${getTableConfig(this._table).columns[c]?.name ?? c}" AS "${c}"`;
604
+ return `${qName}."${getTableConfig(this._table).columns[c]?.name ?? c}" AS "${c}"`;
208
605
  }
209
- return `"${c.name}" AS "${c.alias || c.name}"`;
606
+ return `${c.tableName ? c.tableName + "." : ""}"${c.name}" AS "${c.alias || c.name}"`;
210
607
  }).join(", ");
211
608
  } else {
212
- cols = Object.keys(getTableConfig(this._table).columns).map((c) => `"${getTableConfig(this._table).columns[c].name}" AS "${c}"`).join(", ");
609
+ const qName = getTableConfig(this._table).qualifiedName;
610
+ cols = Object.keys(getTableConfig(this._table).columns).map((c) => `${qName}."${getTableConfig(this._table).columns[c].name}" AS "${c}"`).join(", ");
213
611
  }
214
612
  let query = `SELECT ${cols} FROM ${getTableConfig(this._table).qualifiedName}`;
215
613
  if (this._joins.length > 0) {
216
- query += " " + this._joins.join(" ");
614
+ const joinChunks = this._joins.map((j) => typeof j === "string" ? rawSql(j) : j);
615
+ const combinedJoins = sqlJoin(joinChunks, " ");
616
+ const offset = params.length;
617
+ query += " " + combinedJoins.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
618
+ params.push(...combinedJoins.params);
217
619
  }
218
- const params = [];
219
620
  if (this._where.length > 0) {
220
621
  const combined = sqlJoin(this._where, " AND ");
221
- const offset = 0;
622
+ const offset = params.length;
222
623
  query += " WHERE " + combined.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
223
624
  params.push(...combined.params);
224
625
  }
626
+ if (this._groupBy.length > 0) {
627
+ const qName = getTableConfig(this._table).qualifiedName;
628
+ query += " GROUP BY " + this._groupBy.map((c) => {
629
+ if (typeof c === "string") {
630
+ const dbCol = getTableConfig(this._table).columns[c]?.name ?? c;
631
+ return `${qName}."${dbCol}"`;
632
+ } else if ("sql" in c && "params" in c) {
633
+ const offset = params.length;
634
+ params.push(...c.params);
635
+ return c.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
636
+ } else {
637
+ return `${c.tableName ? c.tableName + "." : ""}"${c.name}"`;
638
+ }
639
+ }).join(", ");
640
+ }
641
+ if (this._having.length > 0) {
642
+ const combined = sqlJoin(this._having, " AND ");
643
+ const offset = params.length;
644
+ query += " HAVING " + combined.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
645
+ params.push(...combined.params);
646
+ }
225
647
  if (this._orderBy.length > 0) {
648
+ const qName = getTableConfig(this._table).qualifiedName;
226
649
  query += " ORDER BY " + this._orderBy.map((o) => {
227
- const dbCol = getTableConfig(this._table).columns[o.column]?.name ?? o.column;
228
- return `"${dbCol}" ${o.dir.toUpperCase()}`;
650
+ if (typeof o.column === "string") {
651
+ const dbCol = getTableConfig(this._table).columns[o.column]?.name ?? o.column;
652
+ return `${qName}."${dbCol}" ${o.dir.toUpperCase()}`;
653
+ } else if ("sql" in o.column && "params" in o.column) {
654
+ const offset = params.length;
655
+ params.push(...o.column.params);
656
+ return `${o.column.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`)} ${o.dir.toUpperCase()}`;
657
+ } else {
658
+ return `${o.column.tableName ? o.column.tableName + "." : ""}"${o.column.name}" ${o.dir.toUpperCase()}`;
659
+ }
229
660
  }).join(", ");
230
661
  }
231
662
  if (this._limit !== undefined) {
@@ -254,12 +685,12 @@ class SelectBuilderIntermediate {
254
685
  return new SelectBuilder(table2, this._executor, this._selection);
255
686
  }
256
687
  }
257
-
258
- class InsertBuilder {
688
+ // ../bungres-orm/src/builders/update.ts
689
+ class UpdateBuilder {
259
690
  _table;
260
691
  _executor;
261
- _values = [];
262
- _onConflict;
692
+ _set = {};
693
+ _where = [];
263
694
  _returning;
264
695
  _comment;
265
696
  constructor(table2, executor) {
@@ -272,17 +703,16 @@ class InsertBuilder {
272
703
  async single() {
273
704
  return this._executor.executeSingle(this);
274
705
  }
275
- values(data) {
276
- const rows = Array.isArray(data) ? data : [data];
277
- this._values.push(...rows);
278
- return this;
279
- }
280
- onConflictDoNothing() {
281
- this._onConflict = "do nothing";
706
+ set(data) {
707
+ this._set = { ...this._set, ...data };
282
708
  return this;
283
709
  }
284
- onConflictDoUpdate(clause) {
285
- this._onConflict = clause;
710
+ where(condition) {
711
+ if (condition && typeof condition === "object" && !("sql" in condition)) {
712
+ this._where.push(parseWhereObject(getTableConfig(this._table), condition));
713
+ } else {
714
+ this._where.push(condition);
715
+ }
286
716
  return this;
287
717
  }
288
718
  returning(...columns) {
@@ -294,28 +724,46 @@ class InsertBuilder {
294
724
  return this;
295
725
  }
296
726
  toSQL() {
297
- if (this._values.length === 0) {
298
- throw new Error("InsertBuilder: no values provided");
727
+ const entries = Object.entries(this._set);
728
+ if (entries.length === 0) {
729
+ throw new Error("UpdateBuilder: no fields to set");
299
730
  }
300
- const keys = Array.from(new Set(this._values.flatMap((v) => Object.keys(v))));
301
731
  const params = [];
302
- const rowPlaceholders = [];
303
- for (const row of this._values) {
304
- const placeholders = [];
305
- for (const key of keys) {
306
- params.push(row[key] ?? null);
307
- placeholders.push(`$${params.length}`);
732
+ const setClauses = entries.map(([key, value]) => {
733
+ const dbCol = getTableConfig(this._table).columns[key]?.name ?? key;
734
+ if (value && typeof value === "object" && "sql" in value && "params" in value) {
735
+ const chunk = value;
736
+ const offset = params.length;
737
+ params.push(...chunk.params);
738
+ return `"${dbCol}" = ${chunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`)}`;
308
739
  }
309
- rowPlaceholders.push(`(${placeholders.join(", ")})`);
310
- }
311
- const colList = keys.map((k) => `"${getTableConfig(this._table).columns[k]?.name ?? k}"`).join(", ");
312
- let query = `INSERT INTO ${getTableConfig(this._table).qualifiedName} (${colList}) VALUES ${rowPlaceholders.join(", ")}`;
313
- if (this._onConflict === "do nothing") {
314
- query += " ON CONFLICT DO NOTHING";
315
- } else if (this._onConflict && typeof this._onConflict === "object") {
740
+ if (value && typeof value === "object" && !(value instanceof Date)) {
741
+ const colType = getTableConfig(this._table).columns[key]?.dataType;
742
+ if (colType === "json" || colType === "jsonb") {
743
+ params.push(value);
744
+ } else if (Array.isArray(value)) {
745
+ const pgArray = "{" + value.map((item) => {
746
+ if (item === null || item === undefined)
747
+ return "NULL";
748
+ if (typeof item === "string")
749
+ return '"' + item.replace(/"/g, "\\\"") + '"';
750
+ return typeof item === "object" ? '"' + JSON.stringify(item).replace(/"/g, "\\\"") + '"' : String(item);
751
+ }).join(",") + "}";
752
+ params.push(pgArray);
753
+ } else {
754
+ params.push(JSON.stringify(value));
755
+ }
756
+ return `"${dbCol}" = $${params.length}`;
757
+ }
758
+ params.push(value);
759
+ return `"${dbCol}" = $${params.length}`;
760
+ });
761
+ let query = `UPDATE ${getTableConfig(this._table).qualifiedName} SET ${setClauses.join(", ")}`;
762
+ if (this._where.length > 0) {
763
+ const combined = sqlJoin(this._where, " AND ");
316
764
  const offset = params.length;
317
- query += " ON CONFLICT " + this._onConflict.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
318
- params.push(...this._onConflict.params);
765
+ query += " WHERE " + combined.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
766
+ params.push(...combined.params);
319
767
  }
320
768
  if (this._returning) {
321
769
  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(", "));
@@ -323,111 +771,10 @@ class InsertBuilder {
323
771
  return { sql: query, params };
324
772
  }
325
773
  }
774
+ // ../bungres-orm/src/builders/relational.ts
775
+ var _relationsCache = new WeakMap;
326
776
 
327
- class UpdateBuilder {
328
- _table;
329
- _executor;
330
- _set = {};
331
- _where = [];
332
- _returning;
333
- _comment;
334
- constructor(table2, executor) {
335
- this._table = table2;
336
- this._executor = executor;
337
- }
338
- then(onfulfilled, onrejected) {
339
- return this._executor.execute(this).then(onfulfilled, onrejected);
340
- }
341
- async single() {
342
- return this._executor.executeSingle(this);
343
- }
344
- set(data) {
345
- this._set = { ...this._set, ...data };
346
- return this;
347
- }
348
- where(condition) {
349
- this._where.push(condition);
350
- return this;
351
- }
352
- returning(...columns) {
353
- this._returning = columns.length > 0 ? columns : ["*"];
354
- return this;
355
- }
356
- comment(tag) {
357
- this._comment = tag;
358
- return this;
359
- }
360
- toSQL() {
361
- const entries = Object.entries(this._set);
362
- if (entries.length === 0) {
363
- throw new Error("UpdateBuilder: no fields to set");
364
- }
365
- const params = [];
366
- const setClauses = entries.map(([key, value]) => {
367
- params.push(value);
368
- const dbCol = getTableConfig(this._table).columns[key]?.name ?? key;
369
- return `"${dbCol}" = $${params.length}`;
370
- });
371
- let query = `UPDATE ${getTableConfig(this._table).qualifiedName} SET ${setClauses.join(", ")}`;
372
- if (this._where.length > 0) {
373
- const combined = sqlJoin(this._where, " AND ");
374
- const offset = params.length;
375
- query += " WHERE " + combined.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
376
- params.push(...combined.params);
377
- }
378
- if (this._returning) {
379
- 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(", "));
380
- }
381
- return { sql: query, params };
382
- }
383
- }
384
-
385
- class DeleteBuilder {
386
- _table;
387
- _executor;
388
- _where = [];
389
- _returning;
390
- _comment;
391
- constructor(table2, executor) {
392
- this._table = table2;
393
- this._executor = executor;
394
- }
395
- then(onfulfilled, onrejected) {
396
- return this._executor.execute(this).then(onfulfilled, onrejected);
397
- }
398
- async single() {
399
- return this._executor.executeSingle(this);
400
- }
401
- where(condition) {
402
- this._where.push(condition);
403
- return this;
404
- }
405
- returning(...columns) {
406
- this._returning = columns.length > 0 ? columns : ["*"];
407
- return this;
408
- }
409
- comment(tag) {
410
- this._comment = tag;
411
- return this;
412
- }
413
- toSQL() {
414
- let query = `DELETE FROM ${getTableConfig(this._table).qualifiedName}`;
415
- const params = [];
416
- if (this._where.length > 0) {
417
- const combined = sqlJoin(this._where, " AND ");
418
- query += " WHERE " + combined.sql;
419
- params.push(...combined.params);
420
- }
421
- if (this._returning) {
422
- 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(", "));
423
- }
424
- return { sql: query, params };
425
- }
426
- }
427
- // ../../node_modules/.bun/@bungres+orm@0.2.0/node_modules/@bungres/orm/src/relational.ts
428
- var _relationsCache = new WeakMap;
429
-
430
- class RelationalQueryBuilder {
777
+ class RelationalQueryBuilder {
431
778
  _executor;
432
779
  _schema;
433
780
  _tableName;
@@ -508,7 +855,7 @@ class RelationalQueryBuilder {
508
855
  const ones = {};
509
856
  const manys = {};
510
857
  const manyToManys = {};
511
- for (const [colName, col2] of Object.entries(tConfig.columns)) {
858
+ for (const [colName2, col2] of Object.entries(tConfig.columns)) {
512
859
  if (col2.references) {
513
860
  const ref = col2.references;
514
861
  const relName = ref.relationName || ref.table;
@@ -517,7 +864,7 @@ class RelationalQueryBuilder {
517
864
  }
518
865
  for (const [otherName, otherTable] of Object.entries(this._schema)) {
519
866
  const otherConfig = otherTable[TableConfigSymbol];
520
- for (const [colName, col2] of Object.entries(otherConfig.columns)) {
867
+ for (const [colName2, col2] of Object.entries(otherConfig.columns)) {
521
868
  if (col2.references && col2.references.table === tableName) {
522
869
  const ref = col2.references;
523
870
  const backRelName = ref.backRelationName || otherName;
@@ -556,10 +903,15 @@ class RelationalQueryBuilder {
556
903
  const jsonFields = [];
557
904
  const lateralJoins = [];
558
905
  const columnsConfig = args.columns;
906
+ const hasTrue = columnsConfig ? Object.values(columnsConfig).some((v) => v === true) : false;
559
907
  for (const [colKey, colConfig] of Object.entries(tableConfig.columns)) {
560
908
  if (columnsConfig) {
561
- if (columnsConfig[colKey] !== true) {
562
- continue;
909
+ if (hasTrue) {
910
+ if (columnsConfig[colKey] !== true)
911
+ continue;
912
+ } else {
913
+ if (columnsConfig[colKey] === false)
914
+ continue;
563
915
  }
564
916
  }
565
917
  jsonFields.push(`'${colKey}', "${alias}"."${colConfig.name}"`);
@@ -618,10 +970,16 @@ class RelationalQueryBuilder {
618
970
  if (joinCondition) {
619
971
  fromSql += ` WHERE ${joinCondition}`;
620
972
  }
621
- if (args.where && args.where.sql) {
973
+ if (args.where) {
622
974
  const offset = params.length;
623
- fromSql += (joinCondition ? " AND " : " WHERE ") + args.where.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
624
- params.push(...args.where.params);
975
+ let whereChunk = args.where;
976
+ if (args.where && !args.where.sql) {
977
+ whereChunk = parseWhereObject(tableConfig, args.where);
978
+ }
979
+ if (whereChunk && whereChunk.sql) {
980
+ fromSql += (joinCondition ? " AND " : " WHERE ") + whereChunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
981
+ params.push(...whereChunk.params);
982
+ }
625
983
  }
626
984
  if (args.orderBy) {
627
985
  if (typeof args.orderBy === "string") {
@@ -630,6 +988,15 @@ class RelationalQueryBuilder {
630
988
  const offset = params.length;
631
989
  fromSql += ` ORDER BY ` + args.orderBy.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
632
990
  params.push(...args.orderBy.params);
991
+ } else {
992
+ const chunks = parseOrderByObject(tableConfig, args.orderBy);
993
+ if (chunks.length > 0) {
994
+ fromSql += ` ORDER BY ` + chunks.map((c) => {
995
+ const offset = params.length;
996
+ params.push(...c.params);
997
+ return c.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
998
+ }).join(", ");
999
+ }
633
1000
  }
634
1001
  }
635
1002
  if (args.limit !== undefined) {
@@ -651,7 +1018,7 @@ class RelationalQueryBuilder {
651
1018
  }
652
1019
  }
653
1020
 
654
- // ../../node_modules/.bun/@bungres+orm@0.2.0/node_modules/@bungres/orm/src/db.ts
1021
+ // ../bungres-orm/src/core/db.ts
655
1022
  function parseDBName(url) {
656
1023
  try {
657
1024
  return new URL(url).pathname.slice(1);
@@ -795,7 +1162,7 @@ class BungresTransaction {
795
1162
  return Array.from(result2);
796
1163
  }
797
1164
  }
798
- function createDB(config2) {
1165
+ function bungres(config2) {
799
1166
  const db2 = new BungresDB(config2);
800
1167
  if (typeof config2 === "object" && config2.schema) {
801
1168
  const schema = config2.schema;
@@ -817,7 +1184,7 @@ function createDB(config2) {
817
1184
  }
818
1185
  return db2;
819
1186
  }
820
- // ../../node_modules/.bun/@bungres+orm@0.2.0/node_modules/@bungres/orm/src/ddl.ts
1187
+ // ../bungres-orm/src/ddl.ts
821
1188
  function generateCreateTable(config2, ifNotExists = true) {
822
1189
  const tableName = config2.schema ? `"${config2.schema}"."${config2.name}"` : `"${config2.name}"`;
823
1190
  const exists = ifNotExists ? " IF NOT EXISTS" : "";
@@ -936,6 +1303,186 @@ function generateDropColumn(tableName, schema, columnName) {
936
1303
  const tbl = schema ? `"${schema}"."${tableName}"` : `"${tableName}"`;
937
1304
  return `ALTER TABLE ${tbl} DROP COLUMN IF EXISTS "${columnName}";`;
938
1305
  }
1306
+ // src/schema-loader.ts
1307
+ async function loadSchemas(patterns, cwd = process.cwd()) {
1308
+ const globs = Array.isArray(patterns) ? patterns : [patterns];
1309
+ const entries = [];
1310
+ for (const pattern of globs) {
1311
+ const glob = new Bun.Glob(pattern);
1312
+ for await (const file of glob.scan({ cwd, absolute: false })) {
1313
+ const absPath = resolve(join(cwd, file));
1314
+ const mod = await import(absPath);
1315
+ for (const [exportName, value] of Object.entries(mod)) {
1316
+ if (isTable(value)) {
1317
+ entries.push({
1318
+ exportName,
1319
+ config: value[TableConfigSymbol],
1320
+ table: value,
1321
+ filePath: absPath
1322
+ });
1323
+ }
1324
+ }
1325
+ }
1326
+ }
1327
+ return entries;
1328
+ }
1329
+ function isTable(value) {
1330
+ return typeof value === "object" && value !== null && TableConfigSymbol in value;
1331
+ }
1332
+
1333
+ // src/utils/colors.ts
1334
+ function colorize(text2, color) {
1335
+ const code2 = Bun.color(color, "ansi");
1336
+ return code2 ? `${code2}${text2}\x1B[0m` : text2;
1337
+ }
1338
+
1339
+ // src/commands/drop.ts
1340
+ async function runDrop(config2, opts = {}) {
1341
+ const schemas2 = await loadSchemas(config2.schema);
1342
+ if (schemas2.length === 0) {
1343
+ console.warn("No table definitions found in schema files.");
1344
+ return;
1345
+ }
1346
+ const sql2 = new Bun.SQL(config2.dbUrl);
1347
+ try {
1348
+ const userSchema = config2.dbSchema;
1349
+ const existingUserTablesResult = await sql2.unsafe(`SELECT table_name FROM information_schema.tables WHERE table_schema = $1`, [userSchema]);
1350
+ const existingTableNames = new Set(existingUserTablesResult.map((r) => r.table_name));
1351
+ const migTableCheck = await sql2.unsafe(`SELECT EXISTS (
1352
+ SELECT 1 FROM information_schema.tables
1353
+ WHERE table_schema = $1 AND table_name = $2
1354
+ ) AS exists`, [config2.migrationsSchema, config2.migrationsTable]);
1355
+ const migrationTableExists = migTableCheck[0]?.exists ?? false;
1356
+ const pushTableCheck = await sql2.unsafe(`SELECT EXISTS (
1357
+ SELECT 1 FROM information_schema.tables
1358
+ WHERE table_schema = $1 AND table_name = $2
1359
+ ) AS exists`, [config2.migrationsSchema, "__bungres_push"]);
1360
+ const pushTableExists = pushTableCheck[0]?.exists ?? false;
1361
+ const tablesToDrop = schemas2.filter((s) => existingTableNames.has(s.config.name));
1362
+ if (tablesToDrop.length === 0 && !migrationTableExists && !pushTableExists) {
1363
+ console.log("No tables to drop (they either don't exist or were already dropped).");
1364
+ return;
1365
+ }
1366
+ const tableNamesToPrint = tablesToDrop.map((s) => (s.config.schema ? s.config.schema + "." : "") + s.config.name);
1367
+ if (migrationTableExists) {
1368
+ tableNamesToPrint.push(`${config2.migrationsSchema}.${config2.migrationsTable}`);
1369
+ }
1370
+ if (pushTableExists) {
1371
+ tableNamesToPrint.push(`${config2.migrationsSchema}.__bungres_push`);
1372
+ }
1373
+ if (!opts.force) {
1374
+ console.warn(colorize(`
1375
+ \u26A0\uFE0F WARNING: This will drop the following tables and ALL their data:
1376
+ `, "red"));
1377
+ for (const name of tableNamesToPrint)
1378
+ console.log(colorize(` - ${name}`, "yellow"));
1379
+ process.stdout.write(colorize(`
1380
+ Are you sure? Type YES to continue: `, "cyan"));
1381
+ for await (const line2 of console) {
1382
+ if (line2.trim().toLowerCase() !== "yes") {
1383
+ console.log("Aborted.");
1384
+ return;
1385
+ }
1386
+ break;
1387
+ }
1388
+ }
1389
+ for (const entry of tablesToDrop) {
1390
+ const ddl = `DROP TABLE IF EXISTS "${entry.config.name}" CASCADE;`;
1391
+ await sql2.unsafe(ddl);
1392
+ console.log(colorize(` \u2713 dropped ${entry.config.name}`, "green"));
1393
+ }
1394
+ if (migrationTableExists) {
1395
+ const qualifiedMigTable = `"${config2.migrationsSchema}"."${config2.migrationsTable}"`;
1396
+ await sql2.unsafe(`DROP TABLE IF EXISTS ${qualifiedMigTable} CASCADE`);
1397
+ console.log(colorize(` \u2713 dropped ${config2.migrationsSchema}.${config2.migrationsTable}`, "green"));
1398
+ }
1399
+ if (pushTableExists) {
1400
+ await sql2.unsafe(`DROP TABLE IF EXISTS "${config2.migrationsSchema}"."__bungres_push" CASCADE`);
1401
+ console.log(colorize(` \u2713 dropped ${config2.migrationsSchema}.__bungres_push`, "green"));
1402
+ }
1403
+ console.log(colorize(`
1404
+ Drop complete.`, "green"));
1405
+ } finally {
1406
+ await sql2.end();
1407
+ }
1408
+ }
1409
+
1410
+ // src/commands/migrate.ts
1411
+ import { join as join2, resolve as resolve2 } from "path";
1412
+ async function runMigrate(config2) {
1413
+ const migrationsDir = resolve2(config2.out);
1414
+ const sql2 = new Bun.SQL(config2.dbUrl);
1415
+ const table2 = config2.migrationsTable;
1416
+ const schema = config2.migrationsSchema;
1417
+ const qualifiedTable = `"${schema}"."${table2}"`;
1418
+ const createSchema = `CREATE SCHEMA IF NOT EXISTS "${schema}";`;
1419
+ const createMigrationsTable = `
1420
+ CREATE TABLE IF NOT EXISTS ${qualifiedTable} (
1421
+ id SERIAL PRIMARY KEY,
1422
+ name TEXT NOT NULL UNIQUE,
1423
+ applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
1424
+ );`.trim();
1425
+ try {
1426
+ await sql2.unsafe(createSchema);
1427
+ await sql2.unsafe(createMigrationsTable);
1428
+ const glob = new Bun.Glob("*.sql");
1429
+ const files = [];
1430
+ for await (const file of glob.scan({ cwd: migrationsDir, absolute: false })) {
1431
+ files.push(file);
1432
+ }
1433
+ files.sort();
1434
+ if (files.length === 0) {
1435
+ console.log(colorize("No migration files found in " + migrationsDir, "yellow"));
1436
+ console.log(colorize("Run `bungres generate` first.", "yellow"));
1437
+ return;
1438
+ }
1439
+ const applied = await sql2.unsafe(`SELECT name FROM ${qualifiedTable}`);
1440
+ const appliedSet = new Set(applied.map((r) => r.name));
1441
+ const pending = files.filter((f) => !appliedSet.has(f));
1442
+ if (pending.length === 0) {
1443
+ console.log(colorize("Everything is up to date.", "green"));
1444
+ return;
1445
+ }
1446
+ console.log(colorize(`
1447
+ Running ${pending.length} pending migration(s)...
1448
+ `, "cyan"));
1449
+ for (const file of pending) {
1450
+ const content = await Bun.file(join2(migrationsDir, file)).text();
1451
+ if (config2.verbose) {
1452
+ console.log(`-- ${file} --
1453
+ ${content}
1454
+ `);
1455
+ }
1456
+ await sql2.transaction(async (txSql) => {
1457
+ const statements = config2.breakpoints ? content.split(/-->statement-breakpoint/g).flatMap((chunk) => chunk.split(";").map((s) => s.trim()).filter(Boolean)) : content.split(";").map((s) => s.trim()).filter(Boolean);
1458
+ for (const stmt of statements) {
1459
+ await txSql.unsafe(stmt + ";");
1460
+ }
1461
+ await txSql.unsafe(`INSERT INTO ${qualifiedTable} (name) VALUES ($1)`, [file]);
1462
+ });
1463
+ console.log(colorize(` \u2713 ${file}`, "green"));
1464
+ }
1465
+ console.log(colorize(`
1466
+ Done.`, "green"));
1467
+ } finally {
1468
+ await sql2.end();
1469
+ }
1470
+ }
1471
+
1472
+ // src/commands/fresh.ts
1473
+ async function runFresh(config2) {
1474
+ console.log("Dropping all tables...");
1475
+ await runDrop(config2, { force: true });
1476
+ console.log(`
1477
+ Re-running migrations...`);
1478
+ await runMigrate(config2);
1479
+ console.log(`
1480
+ Fresh complete.`);
1481
+ }
1482
+
1483
+ // src/commands/generate.ts
1484
+ import { join as join3, resolve as resolve3 } from "path";
1485
+
939
1486
  // src/differ.ts
940
1487
  function diffSchemas(prev, next) {
941
1488
  const statements = [];
@@ -1021,11 +1568,11 @@ function diffSchemas(prev, next) {
1021
1568
  const idxName = idx.name ?? `idx_${tableName}_${idx.columns.join("_")}`;
1022
1569
  if (!prevIdxNames.has(idxName)) {
1023
1570
  const tbl = nextConfig.schema ? `"${nextConfig.schema}"."${tableName}"` : `"${tableName}"`;
1024
- const unique = idx.unique ? "UNIQUE " : "";
1571
+ const unique2 = idx.unique ? "UNIQUE " : "";
1025
1572
  const using = idx.using ? ` USING ${idx.using.toUpperCase()}` : "";
1026
1573
  const cols = idx.columns.map((c) => `"${c}"`).join(", ");
1027
1574
  const where = idx.where ? ` WHERE ${idx.where}` : "";
1028
- statements.push(`CREATE ${unique}INDEX IF NOT EXISTS "${idxName}" ON ${tbl}${using} (${cols})${where};`);
1575
+ statements.push(`CREATE ${unique2}INDEX IF NOT EXISTS "${idxName}" ON ${tbl}${using} (${cols})${where};`);
1029
1576
  summary.push(`CREATE INDEX ${idxName} ON ${tableName}`);
1030
1577
  }
1031
1578
  }
@@ -1036,177 +1583,69 @@ function diffSchemas(prev, next) {
1036
1583
  statements.push(`DROP INDEX IF EXISTS "${idxName}";`);
1037
1584
  summary.push(`DROP INDEX ${idxName}`);
1038
1585
  }
1039
- }
1040
- }
1041
- return { statements, summary, warnings };
1042
- }
1043
- function topoSortConfigs(tables) {
1044
- const byName = new Map(tables.map((t) => [t.name, t]));
1045
- const visited = new Set;
1046
- const result2 = [];
1047
- function deps(config2) {
1048
- return Object.values(config2.columns).map((col2) => col2.references?.table).filter((t) => t !== undefined && byName.has(t) && t !== config2.name);
1049
- }
1050
- function visit(name) {
1051
- if (visited.has(name))
1052
- return;
1053
- visited.add(name);
1054
- const config2 = byName.get(name);
1055
- if (!config2)
1056
- return;
1057
- for (const dep of deps(config2))
1058
- visit(dep);
1059
- result2.push(config2);
1060
- }
1061
- for (const table2 of tables)
1062
- visit(table2.name);
1063
- return result2;
1064
- }
1065
- function diffColumn(prev, next) {
1066
- const changes = [];
1067
- const col2 = `"${next.name}"`;
1068
- if (prev.dataType !== next.dataType) {
1069
- changes.push(`ALTER COLUMN ${col2} TYPE ${next.dataType.toUpperCase()} USING ${col2}::${next.dataType}`);
1070
- }
1071
- if (!prev.notNull && next.notNull) {
1072
- changes.push(`ALTER COLUMN ${col2} SET NOT NULL`);
1073
- }
1074
- if (prev.notNull && !next.notNull) {
1075
- changes.push(`ALTER COLUMN ${col2} DROP NOT NULL`);
1076
- }
1077
- const prevDef = prev.defaultFn ?? (prev.defaultValue !== undefined ? String(prev.defaultValue) : undefined);
1078
- const nextDef = next.defaultFn ?? (next.defaultValue !== undefined ? String(next.defaultValue) : undefined);
1079
- if (prevDef !== nextDef) {
1080
- if (nextDef !== undefined) {
1081
- const val = next.defaultFn ?? formatDefault(next.defaultValue, next.dataType);
1082
- changes.push(`ALTER COLUMN ${col2} SET DEFAULT ${val}`);
1083
- } else {
1084
- changes.push(`ALTER COLUMN ${col2} DROP DEFAULT`);
1085
- }
1086
- }
1087
- return changes;
1088
- }
1089
- function formatDefault(value, dataType) {
1090
- if (value === null || value === undefined)
1091
- return "NULL";
1092
- if (typeof value === "boolean")
1093
- return value ? "TRUE" : "FALSE";
1094
- if (typeof value === "number")
1095
- return String(value);
1096
- if (typeof value === "string") {
1097
- if (dataType === "boolean")
1098
- return value;
1099
- return `'${value.replace(/'/g, "''")}'`;
1100
- }
1101
- return `'${JSON.stringify(value)}'`;
1102
- }
1103
-
1104
- // src/schema-loader.ts
1105
- import { resolve as resolve2, join as join2 } from "path";
1106
- async function loadSchemas(patterns, cwd = process.cwd()) {
1107
- const globs = Array.isArray(patterns) ? patterns : [patterns];
1108
- const entries = [];
1109
- for (const pattern of globs) {
1110
- const glob = new Bun.Glob(pattern);
1111
- for await (const file of glob.scan({ cwd, absolute: false })) {
1112
- const absPath = resolve2(join2(cwd, file));
1113
- const mod = await import(absPath);
1114
- for (const [exportName, value] of Object.entries(mod)) {
1115
- if (isTable(value)) {
1116
- entries.push({
1117
- exportName,
1118
- config: value[TableConfigSymbol],
1119
- table: value,
1120
- filePath: absPath
1121
- });
1122
- }
1123
- }
1124
- }
1125
- }
1126
- return entries;
1127
- }
1128
- function isTable(value) {
1129
- return typeof value === "object" && value !== null && TableConfigSymbol in value;
1130
- }
1131
-
1132
- // src/commands/push.ts
1133
- async function runPush(config2, opts = {}) {
1134
- console.log("@bungres/kit push: loading schemas...");
1135
- const schemas2 = await loadSchemas(config2.schema);
1136
- if (schemas2.length === 0) {
1137
- console.warn("No table definitions found. Check your schema glob pattern.");
1138
- return;
1139
- }
1140
- const sql2 = new Bun.SQL(config2.dbUrl);
1141
- try {
1142
- await sql2.unsafe(`
1143
- CREATE TABLE IF NOT EXISTS public.__bungres_push (
1144
- id SERIAL PRIMARY KEY,
1145
- snapshot JSONB NOT NULL
1146
- );
1147
- `);
1148
- const rows = await sql2.unsafe(`SELECT snapshot FROM public.__bungres_push ORDER BY id DESC LIMIT 1;`);
1149
- let prevSnapshot = {};
1150
- if (rows.length > 0) {
1151
- prevSnapshot = typeof rows[0].snapshot === "string" ? JSON.parse(rows[0].snapshot) : rows[0].snapshot;
1152
- }
1153
- const currentSnapshot = Object.fromEntries(schemas2.map((s) => [s.config.name, s.config]));
1154
- const diff = diffSchemas(prevSnapshot, currentSnapshot);
1155
- if (diff.statements.length === 0) {
1156
- console.log(`
1157
- No schema changes detected. Database is up to date.`);
1158
- return;
1159
- }
1160
- console.log(`
1161
- Changes to apply:`);
1162
- for (const s of diff.summary)
1163
- console.log(` + ${s}`);
1164
- if (diff.warnings && diff.warnings.length > 0) {
1165
- console.warn(`
1166
- \u26A0\uFE0F WARNING: Data Loss Detected!`);
1167
- for (const w of diff.warnings)
1168
- console.warn(` ! ${w}`);
1169
- console.warn(`
1170
- These changes will be immediately executed against the database!`);
1171
- }
1172
- if (!opts.force) {
1173
- process.stdout.write(`
1174
- Are you sure you want to push these changes? Type YES to continue: `);
1175
- const answer = await readLine();
1176
- if (answer.trim().toLowerCase() !== "yes") {
1177
- console.log("Aborted.");
1178
- return;
1179
- }
1180
- }
1181
- console.log(`
1182
- Pushing changes...`);
1183
- await sql2.unsafe(`CREATE EXTENSION IF NOT EXISTS "pgcrypto";`);
1184
- for (const stmt of diff.statements) {
1185
- if (config2.verbose) {
1186
- console.log(`-- ${stmt}`);
1187
- }
1188
- await sql2.unsafe(stmt);
1189
- }
1190
- await sql2.unsafe(`INSERT INTO public.__bungres_push (snapshot) VALUES ($1);`, [JSON.stringify(currentSnapshot)]);
1191
- console.log(`
1192
- Push complete.`);
1193
- } finally {
1194
- await sql2.end();
1586
+ }
1195
1587
  }
1588
+ return { statements, summary, warnings };
1196
1589
  }
1197
- async function readLine() {
1198
- const buf = Buffer.alloc(256);
1199
- const n = fs.readSync(0, buf, 0, 256, null);
1200
- return buf.subarray(0, n).toString().trim();
1590
+ function topoSortConfigs(tables) {
1591
+ const byName = new Map(tables.map((t) => [t.name, t]));
1592
+ const visited = new Set;
1593
+ const result2 = [];
1594
+ function deps(config2) {
1595
+ return Object.values(config2.columns).map((col2) => col2.references?.table).filter((t) => t !== undefined && byName.has(t) && t !== config2.name);
1596
+ }
1597
+ function visit(name) {
1598
+ if (visited.has(name))
1599
+ return;
1600
+ visited.add(name);
1601
+ const config2 = byName.get(name);
1602
+ if (!config2)
1603
+ return;
1604
+ for (const dep of deps(config2))
1605
+ visit(dep);
1606
+ result2.push(config2);
1607
+ }
1608
+ for (const table2 of tables)
1609
+ visit(table2.name);
1610
+ return result2;
1201
1611
  }
1202
-
1203
- // src/commands/generate.ts
1204
- import { join as join3, resolve as resolve3 } from "path";
1205
-
1206
- // src/utils/colors.ts
1207
- function colorize(text2, color) {
1208
- const code2 = Bun.color(color, "ansi");
1209
- return code2 ? `${code2}${text2}\x1B[0m` : text2;
1612
+ function diffColumn(prev, next) {
1613
+ const changes = [];
1614
+ const col2 = `"${next.name}"`;
1615
+ if (prev.dataType !== next.dataType) {
1616
+ changes.push(`ALTER COLUMN ${col2} TYPE ${next.dataType.toUpperCase()} USING ${col2}::${next.dataType}`);
1617
+ }
1618
+ if (!prev.notNull && next.notNull) {
1619
+ changes.push(`ALTER COLUMN ${col2} SET NOT NULL`);
1620
+ }
1621
+ if (prev.notNull && !next.notNull) {
1622
+ changes.push(`ALTER COLUMN ${col2} DROP NOT NULL`);
1623
+ }
1624
+ const prevDef = prev.defaultFn ?? (prev.defaultValue !== undefined ? String(prev.defaultValue) : undefined);
1625
+ const nextDef = next.defaultFn ?? (next.defaultValue !== undefined ? String(next.defaultValue) : undefined);
1626
+ if (prevDef !== nextDef) {
1627
+ if (nextDef !== undefined) {
1628
+ const val = next.defaultFn ?? formatDefault(next.defaultValue, next.dataType);
1629
+ changes.push(`ALTER COLUMN ${col2} SET DEFAULT ${val}`);
1630
+ } else {
1631
+ changes.push(`ALTER COLUMN ${col2} DROP DEFAULT`);
1632
+ }
1633
+ }
1634
+ return changes;
1635
+ }
1636
+ function formatDefault(value, dataType) {
1637
+ if (value === null || value === undefined)
1638
+ return "NULL";
1639
+ if (typeof value === "boolean")
1640
+ return value ? "TRUE" : "FALSE";
1641
+ if (typeof value === "number")
1642
+ return String(value);
1643
+ if (typeof value === "string") {
1644
+ if (dataType === "boolean")
1645
+ return value;
1646
+ return `'${value.replace(/'/g, "''")}'`;
1647
+ }
1648
+ return `'${JSON.stringify(value)}'`;
1210
1649
  }
1211
1650
 
1212
1651
  // src/commands/generate.ts
@@ -1218,7 +1657,7 @@ async function runGenerate(config2, name) {
1218
1657
  console.warn(colorize("No table definitions found. Check your schema glob pattern.", "yellow"));
1219
1658
  return;
1220
1659
  }
1221
- const migrationsDir = resolve3(config2.migrationsDir);
1660
+ const migrationsDir = resolve3(config2.out);
1222
1661
  await Bun.$`mkdir -p ${migrationsDir}`.quiet();
1223
1662
  const currentSnapshot = Object.fromEntries(schemas2.map((s) => [s.config.name, s.config]));
1224
1663
  const snapshotPath = join3(migrationsDir, SNAPSHOT_FILE);
@@ -1310,69 +1749,93 @@ function topoSort(schemas2) {
1310
1749
  return result2;
1311
1750
  }
1312
1751
 
1313
- // src/commands/migrate.ts
1314
- import { join as join4, resolve as resolve4 } from "path";
1315
- var MIGRATIONS_TABLE = "__bungres_migrations";
1316
- var CREATE_MIGRATIONS_TABLE = `
1317
- CREATE TABLE IF NOT EXISTS "${MIGRATIONS_TABLE}" (
1318
- id SERIAL PRIMARY KEY,
1319
- name TEXT NOT NULL UNIQUE,
1320
- applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
1321
- );
1322
- `.trim();
1323
- async function runMigrate(config2) {
1324
- const migrationsDir = resolve4(config2.migrationsDir);
1325
- const sql2 = new Bun.SQL(config2.dbUrl);
1752
+ // src/commands/init.ts
1753
+ import { existsSync } from "fs";
1754
+ import { mkdir } from "fs/promises";
1755
+ import { join as join4 } from "path";
1756
+ async function runInit(cwd = process.cwd()) {
1757
+ const configPath = join4(cwd, "bungres.config.ts");
1758
+ const dbDir = join4(cwd, "src", "db");
1759
+ if (existsSync(configPath)) {
1760
+ console.log("Config file already exists at bungres.config.ts");
1761
+ return;
1762
+ }
1326
1763
  try {
1327
- await sql2.unsafe(CREATE_MIGRATIONS_TABLE);
1328
- const glob = new Bun.Glob("*.sql");
1329
- const files = [];
1330
- for await (const file of glob.scan({ cwd: migrationsDir, absolute: false })) {
1331
- files.push(file);
1332
- }
1333
- files.sort();
1334
- if (files.length === 0) {
1335
- console.log(colorize("No migration files found in " + migrationsDir, "yellow"));
1336
- console.log(colorize("Run `bungres generate` first.", "yellow"));
1337
- return;
1338
- }
1339
- const applied = await sql2.unsafe(`SELECT name FROM "${MIGRATIONS_TABLE}"`);
1340
- const appliedSet = new Set(applied.map((r) => r.name));
1341
- const pending = files.filter((f) => !appliedSet.has(f));
1342
- if (pending.length === 0) {
1343
- console.log(colorize("Everything is up to date.", "green"));
1344
- return;
1345
- }
1346
- console.log(colorize(`
1347
- Running ${pending.length} pending migration(s)...
1348
- `, "cyan"));
1349
- for (const file of pending) {
1350
- const content = await Bun.file(join4(migrationsDir, file)).text();
1351
- if (config2.verbose) {
1352
- console.log(`-- ${file} --
1353
- ${content}
1354
- `);
1355
- }
1356
- await sql2.transaction(async (txSql) => {
1357
- const statements = content.split(";").map((s) => s.trim()).filter(Boolean);
1358
- for (const stmt of statements) {
1359
- await txSql.unsafe(stmt + ";");
1360
- }
1361
- await txSql.unsafe(`INSERT INTO "${MIGRATIONS_TABLE}" (name) VALUES ($1)`, [file]);
1362
- });
1363
- console.log(colorize(` \u2713 ${file}`, "green"));
1364
- }
1365
- console.log(colorize(`
1366
- Done.`, "green"));
1367
- } finally {
1368
- await sql2.end();
1764
+ await mkdir(dbDir, { recursive: true });
1765
+ console.log("Created src/db directory");
1766
+ } catch (e) {
1767
+ console.error("Failed to create src/db directory:", e);
1768
+ return;
1769
+ }
1770
+ const configContent = `import { defineConfig } from "@bungres/kit";
1771
+
1772
+ export default defineConfig({
1773
+ schema: "./src/db/schema.ts",
1774
+ out: "./bungres",
1775
+ dbCredentials: {
1776
+ url: process.env.DATABASE_URL!,
1777
+ },
1778
+ });
1779
+ `;
1780
+ try {
1781
+ await Bun.write(configPath, configContent);
1782
+ console.log("Created bungres.config.ts");
1783
+ } catch (e) {
1784
+ console.error("Failed to create config file:", e);
1785
+ return;
1786
+ }
1787
+ const schemaContent = `import {
1788
+ uuid,
1789
+ varchar,
1790
+ timestamptz,
1791
+ table,
1792
+ unique,
1793
+ index
1794
+ } from "@bungres/orm";
1795
+
1796
+ export const users = table("users", {
1797
+ id: uuid({ primaryKey: true }),
1798
+ name: varchar({ length: 255, notNull: true }),
1799
+ email: varchar({ length: 255, notNull: true }),
1800
+ createdAt: timestamptz({ notNull: true, defaultRaw: "NOW()" }),
1801
+ }, (t) => [
1802
+ unique().on(t.email),
1803
+ index().on(t.email),
1804
+ ]);
1805
+ `;
1806
+ try {
1807
+ await Bun.write(join4(dbDir, "schema.ts"), schemaContent);
1808
+ console.log("Created src/db/schema.ts with example table");
1809
+ } catch (e) {
1810
+ console.error("Failed to create schema file:", e);
1369
1811
  }
1812
+ const clientContent = `import { bungres } from "@bungres/orm";
1813
+ import * as schema from "./schema";
1814
+
1815
+ const url = Bun.env.DATABASE_URL || "postgres://postgres:postgres@localhost:5432/postgres";
1816
+
1817
+ export const db = bungres({ url, schema });
1818
+ `;
1819
+ try {
1820
+ await Bun.write(join4(dbDir, "client.ts"), clientContent);
1821
+ console.log("Created src/db/client.ts");
1822
+ } catch (e) {
1823
+ console.error("Failed to create client file:", e);
1824
+ }
1825
+ console.log(`
1826
+ \u2728 Bungres project initialized!`);
1827
+ console.log(`
1828
+ Next steps:`);
1829
+ console.log(" 1. Set DATABASE_URL in your .env file");
1830
+ console.log(" 2. Edit src/db/schema.ts to define your tables");
1831
+ console.log(" 3. Run 'bungres generate' to create migrations");
1832
+ console.log(" 4. Run 'bungres migrate' to apply migrations");
1370
1833
  }
1371
1834
 
1372
1835
  // src/commands/pull.ts
1373
- import { resolve as resolve5, join as join5 } from "path";
1836
+ import { resolve as resolve4, join as join5 } from "path";
1374
1837
  async function introspectDb(sql2, dbSchema) {
1375
- const columns = await sql2.unsafe(`SELECT
1838
+ const columns2 = await sql2.unsafe(`SELECT
1376
1839
  c.table_name,
1377
1840
  c.column_name,
1378
1841
  c.data_type,
@@ -1406,7 +1869,7 @@ async function introspectDb(sql2, dbSchema) {
1406
1869
  const indexes = await sql2.unsafe(`SELECT tablename, indexname, indexdef
1407
1870
  FROM pg_indexes
1408
1871
  WHERE schemaname = $1`, [dbSchema]);
1409
- return groupByTable(columns, constraints, indexes);
1872
+ return groupByTable(columns2, constraints, indexes);
1410
1873
  }
1411
1874
  async function runPull(config2) {
1412
1875
  console.log("@bungres/kit pull: introspecting database...");
@@ -1418,7 +1881,7 @@ async function runPull(config2) {
1418
1881
  console.log("No tables found in schema:", dbSchema);
1419
1882
  return;
1420
1883
  }
1421
- const outDir = resolve5(config2.outDir);
1884
+ const outDir = resolve4(config2.outDir);
1422
1885
  await Bun.$`mkdir -p ${outDir}`.quiet();
1423
1886
  const outPath = join5(outDir, "schema.ts");
1424
1887
  const code2 = generateSchemaTS(tableMap, dbSchema);
@@ -1429,9 +1892,9 @@ async function runPull(config2) {
1429
1892
  await sql2.end();
1430
1893
  }
1431
1894
  }
1432
- function groupByTable(columns, constraints, indexes) {
1895
+ function groupByTable(columns2, constraints, indexes) {
1433
1896
  const map = new Map;
1434
- for (const col2 of columns) {
1897
+ for (const col2 of columns2) {
1435
1898
  if (!map.has(col2.table_name)) {
1436
1899
  map.set(col2.table_name, {
1437
1900
  tableName: col2.table_name,
@@ -1467,7 +1930,7 @@ function generateSchemaTS(tableMap, dbSchema) {
1467
1930
  `// Generated at: ${new Date().toISOString()}`,
1468
1931
  ``,
1469
1932
  `import {`,
1470
- ` snakeCase,`,
1933
+ ` table,`,
1471
1934
  ` text, varchar, char, integer, bigint, smallint,`,
1472
1935
  ` serial, bigserial, boolean, real, doublePrecision,`,
1473
1936
  ` numeric, decimal, json, jsonb,`,
@@ -1479,7 +1942,7 @@ function generateSchemaTS(tableMap, dbSchema) {
1479
1942
  ];
1480
1943
  for (const [, table2] of tableMap) {
1481
1944
  const varName = toCamelCase(table2.tableName);
1482
- lines.push(`export const ${varName} = snakeCase.table("${table2.tableName}", {`);
1945
+ lines.push(`export const ${varName} = table("${table2.tableName}", {`);
1483
1946
  for (const col2 of table2.columns) {
1484
1947
  const colExpr = buildColumnExpression(col2);
1485
1948
  lines.push(` ${toCamelCase(col2.name)}: ${colExpr},`);
@@ -1625,126 +2088,76 @@ function toCamelCase(str) {
1625
2088
  return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
1626
2089
  }
1627
2090
 
1628
- // src/commands/status.ts
1629
- import { resolve as resolve6 } from "path";
1630
- var MIGRATIONS_TABLE2 = "__bungres_migrations";
1631
- async function runStatus(config2) {
1632
- const migrationsDir = resolve6(config2.migrationsDir);
1633
- const sql2 = new Bun.SQL(config2.dbUrl);
1634
- try {
1635
- const tableCheck = await sql2.unsafe(`SELECT EXISTS (
1636
- SELECT 1 FROM information_schema.tables
1637
- WHERE table_name = $1
1638
- ) AS exists`, [MIGRATIONS_TABLE2]);
1639
- const trackingExists = tableCheck[0]?.exists ?? false;
1640
- const glob = new Bun.Glob("*.sql");
1641
- const files = [];
1642
- for await (const file of glob.scan({ cwd: migrationsDir, absolute: false })) {
1643
- files.push(file);
1644
- }
1645
- files.sort();
1646
- if (files.length === 0) {
1647
- console.log(colorize("No migration files found. Run `bungres generate` first.", "yellow"));
1648
- return;
1649
- }
1650
- let appliedSet = new Set;
1651
- if (trackingExists) {
1652
- const applied = await sql2.unsafe(`SELECT name FROM "${MIGRATIONS_TABLE2}" ORDER BY applied_at`);
1653
- appliedSet = new Set(applied.map((r) => r.name));
1654
- }
1655
- console.log(colorize(`
1656
- Migration status:
1657
- `, "cyan"));
1658
- let pendingCount = 0;
1659
- for (const file of files) {
1660
- const isApplied = appliedSet.has(file);
1661
- const status = isApplied ? colorize("\u2713 applied ", "green") : colorize("\u2717 pending ", "yellow");
1662
- if (!isApplied)
1663
- pendingCount++;
1664
- console.log(` ${status} ${file}`);
1665
- }
1666
- console.log(`
1667
- ${colorize(appliedSet.size.toString(), "green")} applied, ${colorize(pendingCount.toString(), "yellow")} pending.
1668
- `);
1669
- } finally {
1670
- await sql2.end();
1671
- }
1672
- }
1673
-
1674
- // src/commands/drop.ts
1675
- async function runDrop(config2, opts = {}) {
2091
+ // src/commands/push.ts
2092
+ import * as fs from "fs";
2093
+ async function runPush(config2, opts = {}) {
2094
+ console.log("@bungres/kit push: loading schemas...");
1676
2095
  const schemas2 = await loadSchemas(config2.schema);
1677
2096
  if (schemas2.length === 0) {
1678
- console.warn("No table definitions found in schema files.");
2097
+ console.warn("No table definitions found. Check your schema glob pattern.");
1679
2098
  return;
1680
2099
  }
1681
2100
  const sql2 = new Bun.SQL(config2.dbUrl);
1682
2101
  try {
1683
- const existingTablesResult = await sql2`
1684
- SELECT table_name
1685
- FROM information_schema.tables
1686
- WHERE table_schema = 'public' OR table_schema = current_schema()
1687
- `;
1688
- const existingTableNames = new Set(existingTablesResult.map((row) => row.table_name));
1689
- const migrationTableExists = existingTableNames.has("__bungres_migrations");
1690
- const pushTableExists = existingTableNames.has("__bungres_push");
1691
- const tablesToDrop = schemas2.filter((s) => existingTableNames.has(s.config.name));
1692
- if (tablesToDrop.length === 0 && !migrationTableExists && !pushTableExists) {
1693
- console.log("No tables to drop (they either don't exist or were already dropped).");
1694
- return;
2102
+ await sql2.unsafe(`
2103
+ CREATE TABLE IF NOT EXISTS public.__bungres_push (
2104
+ id SERIAL PRIMARY KEY,
2105
+ snapshot JSONB NOT NULL
2106
+ );
2107
+ `);
2108
+ const rows = await sql2.unsafe(`SELECT snapshot FROM public.__bungres_push ORDER BY id DESC LIMIT 1;`);
2109
+ let prevSnapshot = {};
2110
+ if (rows.length > 0) {
2111
+ prevSnapshot = typeof rows[0].snapshot === "string" ? JSON.parse(rows[0].snapshot) : rows[0].snapshot;
1695
2112
  }
1696
- const tableNamesToPrint = tablesToDrop.map((s) => (s.config.schema ? s.config.schema + "." : "") + s.config.name);
1697
- if (migrationTableExists) {
1698
- tableNamesToPrint.push("__bungres_migrations");
2113
+ const currentSnapshot = Object.fromEntries(schemas2.map((s) => [s.config.name, s.config]));
2114
+ const diff = diffSchemas(prevSnapshot, currentSnapshot);
2115
+ if (diff.statements.length === 0) {
2116
+ console.log(`
2117
+ No schema changes detected. Database is up to date.`);
2118
+ return;
1699
2119
  }
1700
- if (pushTableExists) {
1701
- tableNamesToPrint.push("__bungres_push");
2120
+ console.log(`
2121
+ Changes to apply:`);
2122
+ for (const s of diff.summary)
2123
+ console.log(` + ${s}`);
2124
+ if (diff.warnings && diff.warnings.length > 0) {
2125
+ console.warn(`
2126
+ \u26A0\uFE0F WARNING: Data Loss Detected!`);
2127
+ for (const w of diff.warnings)
2128
+ console.warn(` ! ${w}`);
2129
+ console.warn(`
2130
+ These changes will be immediately executed against the database!`);
1702
2131
  }
1703
2132
  if (!opts.force) {
1704
- console.warn(colorize(`
1705
- \u26A0\uFE0F WARNING: This will drop the following tables and ALL their data:
1706
- `, "red"));
1707
- for (const name of tableNamesToPrint)
1708
- console.log(colorize(` - ${name}`, "yellow"));
1709
- process.stdout.write(colorize(`
1710
- Are you sure? Type YES to continue: `, "cyan"));
1711
- for await (const line2 of console) {
1712
- if (line2.trim().toLowerCase() !== "yes") {
1713
- console.log("Aborted.");
1714
- return;
1715
- }
1716
- break;
2133
+ process.stdout.write(`
2134
+ Are you sure you want to push these changes? Type YES to continue: `);
2135
+ const answer = await readLine();
2136
+ if (answer.trim().toLowerCase() !== "yes") {
2137
+ console.log("Aborted.");
2138
+ return;
1717
2139
  }
1718
2140
  }
1719
- for (const entry of tablesToDrop) {
1720
- const ddl = `DROP TABLE IF EXISTS "${entry.config.name}" CASCADE;`;
1721
- await sql2.unsafe(ddl);
1722
- console.log(colorize(` \u2713 dropped ${entry.config.name}`, "green"));
1723
- }
1724
- if (migrationTableExists) {
1725
- await sql2.unsafe("DROP TABLE IF EXISTS public.__bungres_migrations CASCADE");
1726
- console.log(colorize(` \u2713 dropped __bungres_migrations`, "green"));
1727
- }
1728
- if (pushTableExists) {
1729
- await sql2.unsafe("DROP TABLE IF EXISTS public.__bungres_push CASCADE");
1730
- console.log(colorize(` \u2713 dropped __bungres_push`, "green"));
2141
+ console.log(`
2142
+ Pushing changes...`);
2143
+ await sql2.unsafe(`CREATE EXTENSION IF NOT EXISTS "pgcrypto";`);
2144
+ for (const stmt of diff.statements) {
2145
+ if (config2.verbose) {
2146
+ console.log(`-- ${stmt}`);
2147
+ }
2148
+ await sql2.unsafe(stmt);
1731
2149
  }
1732
- console.log(colorize(`
1733
- Drop complete.`, "green"));
2150
+ await sql2.unsafe(`INSERT INTO public.__bungres_push (snapshot) VALUES ($1);`, [JSON.stringify(currentSnapshot)]);
2151
+ console.log(`
2152
+ Push complete.`);
1734
2153
  } finally {
1735
2154
  await sql2.end();
1736
2155
  }
1737
2156
  }
1738
-
1739
- // src/commands/fresh.ts
1740
- async function runFresh(config2) {
1741
- console.log("Dropping all tables...");
1742
- await runDrop(config2, { force: true });
1743
- console.log(`
1744
- Re-running migrations...`);
1745
- await runMigrate(config2);
1746
- console.log(`
1747
- Fresh complete.`);
2157
+ async function readLine() {
2158
+ const buf = Buffer.alloc(256);
2159
+ const n = fs.readSync(0, buf, 0, 256, null);
2160
+ return buf.subarray(0, n).toString().trim();
1748
2161
  }
1749
2162
 
1750
2163
  // src/commands/refresh.ts
@@ -1771,13 +2184,13 @@ Refresh complete. All tables are now empty.`, "green"));
1771
2184
  }
1772
2185
 
1773
2186
  // src/commands/seed.ts
1774
- import { resolve as resolve7 } from "path";
2187
+ import { resolve as resolve5 } from "path";
1775
2188
  async function runSeed(config2) {
1776
2189
  if (!config2.seed) {
1777
2190
  console.log(colorize(`No seed file configured in bungres.config.ts`, "yellow"));
1778
2191
  return;
1779
2192
  }
1780
- const seedPath = resolve7(process.cwd(), config2.seed);
2193
+ const seedPath = resolve5(process.cwd(), config2.seed);
1781
2194
  const file = Bun.file(seedPath);
1782
2195
  if (!await file.exists()) {
1783
2196
  console.error(colorize(`Seed file not found at ${seedPath}`, "red"));
@@ -1789,7 +2202,7 @@ Running seeder: ${config2.seed}...`, "cyan"));
1789
2202
  cwd: process.cwd(),
1790
2203
  stdout: "inherit",
1791
2204
  stderr: "inherit",
1792
- env: { ...process.env, DATABASE_URL: config2.dbUrl }
2205
+ env: { ...Bun.env, DATABASE_URL: config2.dbUrl }
1793
2206
  });
1794
2207
  const exitCode = await proc.exited;
1795
2208
  if (exitCode === 0) {
@@ -1802,6 +2215,54 @@ Seed failed with exit code ${exitCode}.`);
1802
2215
  }
1803
2216
  }
1804
2217
 
2218
+ // src/commands/status.ts
2219
+ import { resolve as resolve6 } from "path";
2220
+ async function runStatus(config2) {
2221
+ const migrationsDir = resolve6(config2.out);
2222
+ const sql2 = new Bun.SQL(config2.dbUrl);
2223
+ const table2 = config2.migrationsTable;
2224
+ const schema = config2.migrationsSchema;
2225
+ const qualifiedTable = `"${schema}"."${table2}"`;
2226
+ try {
2227
+ const tableCheck = await sql2.unsafe(`SELECT EXISTS (
2228
+ SELECT 1 FROM information_schema.tables
2229
+ WHERE table_schema = $1 AND table_name = $2
2230
+ ) AS exists`, [schema, table2]);
2231
+ const trackingExists = tableCheck[0]?.exists ?? false;
2232
+ const glob = new Bun.Glob("*.sql");
2233
+ const files = [];
2234
+ for await (const file of glob.scan({ cwd: migrationsDir, absolute: false })) {
2235
+ files.push(file);
2236
+ }
2237
+ files.sort();
2238
+ if (files.length === 0) {
2239
+ console.log(colorize("No migration files found. Run `bungres generate` first.", "yellow"));
2240
+ return;
2241
+ }
2242
+ let appliedSet = new Set;
2243
+ if (trackingExists) {
2244
+ const applied = await sql2.unsafe(`SELECT name FROM ${qualifiedTable} ORDER BY applied_at`);
2245
+ appliedSet = new Set(applied.map((r) => r.name));
2246
+ }
2247
+ console.log(colorize(`
2248
+ Migration status:
2249
+ `, "cyan"));
2250
+ let pendingCount = 0;
2251
+ for (const file of files) {
2252
+ const isApplied = appliedSet.has(file);
2253
+ const status = isApplied ? colorize("\u2713 applied ", "green") : colorize("\u2717 pending ", "yellow");
2254
+ if (!isApplied)
2255
+ pendingCount++;
2256
+ console.log(` ${status} ${file}`);
2257
+ }
2258
+ console.log(`
2259
+ ${colorize(appliedSet.size.toString(), "green")} applied, ${colorize(pendingCount.toString(), "yellow")} pending.
2260
+ `);
2261
+ } finally {
2262
+ await sql2.end();
2263
+ }
2264
+ }
2265
+
1805
2266
  // src/commands/studio.ts
1806
2267
  async function runStudio(config2) {
1807
2268
  const schemas2 = await loadSchemas(config2.schema);
@@ -1813,8 +2274,8 @@ async function runStudio(config2) {
1813
2274
  for (const s of schemas2) {
1814
2275
  schemaObj2[s.exportName] = s.table;
1815
2276
  }
1816
- const db2 = createDB({ url: config2.dbUrl, schema: schemaObj2 });
1817
- const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 5555;
2277
+ const db2 = bungres({ url: config2.dbUrl, schema: schemaObj2 });
2278
+ const port = Bun.env.PORT ? parseInt(Bun.env.PORT, 10) : 5555;
1818
2279
  const server = Bun.serve({
1819
2280
  port,
1820
2281
  async fetch(req) {
@@ -1840,8 +2301,19 @@ async function runStudio(config2) {
1840
2301
  return new Response("Table not found", { status: 404 });
1841
2302
  }
1842
2303
  try {
1843
- const data = await db2.select().from(schema.table).limit(100);
1844
- return new Response(JSON.stringify(data), {
2304
+ const page = parseInt(url.searchParams.get("page") || "1", 10);
2305
+ const limit = parseInt(url.searchParams.get("limit") || "50", 10);
2306
+ const offset = (page - 1) * limit;
2307
+ const countResult = await db2.select({ count: schema.table }).from(schema.table);
2308
+ const total = Array.isArray(countResult) ? countResult.length : 0;
2309
+ const data = await db2.select().from(schema.table).limit(limit).offset(offset);
2310
+ return new Response(JSON.stringify({
2311
+ data,
2312
+ total,
2313
+ page,
2314
+ limit,
2315
+ totalPages: Math.ceil(total / limit)
2316
+ }), {
1845
2317
  headers: { "Content-Type": "application/json" }
1846
2318
  });
1847
2319
  } catch (e) {
@@ -1891,18 +2363,26 @@ var htmlTemplate = `
1891
2363
  <meta charset="UTF-8">
1892
2364
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1893
2365
  <title>Bungres Studio</title>
1894
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
2366
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
1895
2367
  <style>
1896
2368
  :root {
1897
- --bg-color: #0d1117;
1898
- --panel-bg: #161b22;
1899
- --border-color: #30363d;
1900
- --text-main: #c9d1d9;
1901
- --text-muted: #8b949e;
1902
- --accent-color: #58a6ff;
1903
- --accent-hover: #1f6feb;
1904
- --header-bg: #161b22;
1905
- --row-hover: #1f2428;
2369
+ --bg-color: #09090b;
2370
+ --panel-bg: rgba(24, 24, 27, 0.7);
2371
+ --border-color: #27272a;
2372
+ --text-main: #f4f4f5;
2373
+ --text-muted: #a1a1aa;
2374
+ --accent-color: #8b5cf6;
2375
+ --accent-hover: #7c3aed;
2376
+ --header-bg: rgba(9, 9, 11, 0.85);
2377
+ --row-hover: rgba(39, 39, 42, 0.8);
2378
+ --glass-border: rgba(255, 255, 255, 0.08);
2379
+
2380
+ --type-number: #f472b6;
2381
+ --type-string: #60a5fa;
2382
+ --type-boolean: #34d399;
2383
+ --type-date: #c084fc;
2384
+ --type-json: #fbbf24;
2385
+ --type-null: #71717a;
1906
2386
  }
1907
2387
 
1908
2388
  * { box-sizing: border-box; }
@@ -1910,7 +2390,7 @@ var htmlTemplate = `
1910
2390
  body {
1911
2391
  margin: 0;
1912
2392
  padding: 0;
1913
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
2393
+ font-family: 'Outfit', -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
1914
2394
  background-color: var(--bg-color);
1915
2395
  color: var(--text-main);
1916
2396
  display: flex;
@@ -1920,56 +2400,91 @@ var htmlTemplate = `
1920
2400
 
1921
2401
  /* Sidebar */
1922
2402
  .sidebar {
1923
- width: 250px;
2403
+ width: 280px;
1924
2404
  background-color: var(--panel-bg);
2405
+ backdrop-filter: blur(12px);
2406
+ -webkit-backdrop-filter: blur(12px);
1925
2407
  border-right: 1px solid var(--border-color);
1926
2408
  display: flex;
1927
2409
  flex-direction: column;
2410
+ box-shadow: 4px 0 24px rgba(0,0,0,0.2);
2411
+ z-index: 20;
1928
2412
  }
1929
2413
 
1930
2414
  .sidebar-header {
1931
- padding: 20px;
1932
- border-bottom: 1px solid var(--border-color);
2415
+ padding: 24px;
2416
+ border-bottom: 1px solid var(--glass-border);
1933
2417
  display: flex;
1934
2418
  align-items: center;
1935
- gap: 10px;
2419
+ gap: 12px;
2420
+ background: linear-gradient(to bottom, rgba(255,255,255,0.03), transparent);
1936
2421
  }
1937
2422
 
1938
2423
  .sidebar-header h1 {
1939
2424
  margin: 0;
1940
- font-size: 16px;
2425
+ font-size: 18px;
1941
2426
  font-weight: 600;
1942
- color: white;
2427
+ background: linear-gradient(to right, #fff, #a1a1aa);
2428
+ -webkit-background-clip: text;
2429
+ -webkit-text-fill-color: transparent;
2430
+ letter-spacing: -0.5px;
2431
+ }
2432
+
2433
+ .sidebar-header svg {
2434
+ color: var(--accent-color);
2435
+ filter: drop-shadow(0 0 8px rgba(139, 92, 246, 0.5));
1943
2436
  }
1944
2437
 
1945
2438
  .table-list {
1946
2439
  flex: 1;
1947
2440
  overflow-y: auto;
1948
- padding: 10px 0;
2441
+ padding: 16px 12px;
1949
2442
  list-style: none;
1950
2443
  margin: 0;
1951
2444
  }
1952
2445
 
2446
+ .table-list::-webkit-scrollbar {
2447
+ width: 6px;
2448
+ }
2449
+ .table-list::-webkit-scrollbar-thumb {
2450
+ background: #3f3f46;
2451
+ border-radius: 4px;
2452
+ }
2453
+
1953
2454
  .table-item {
1954
- padding: 8px 20px;
2455
+ padding: 12px 16px;
1955
2456
  cursor: pointer;
1956
- font-size: 14px;
2457
+ font-size: 15px;
2458
+ font-weight: 500;
1957
2459
  color: var(--text-muted);
1958
- transition: all 0.2s ease;
2460
+ border-radius: 8px;
2461
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1959
2462
  display: flex;
1960
2463
  align-items: center;
1961
- gap: 8px;
2464
+ gap: 10px;
2465
+ margin-bottom: 4px;
2466
+ border: 1px solid transparent;
1962
2467
  }
1963
2468
 
1964
2469
  .table-item:hover {
1965
- background-color: var(--row-hover);
2470
+ background-color: rgba(255, 255, 255, 0.05);
1966
2471
  color: var(--text-main);
2472
+ transform: translateX(4px);
1967
2473
  }
1968
2474
 
1969
2475
  .table-item.active {
1970
- background-color: rgba(88, 166, 255, 0.1);
2476
+ background-color: rgba(139, 92, 246, 0.15);
2477
+ color: #fff;
2478
+ border: 1px solid rgba(139, 92, 246, 0.3);
2479
+ box-shadow: 0 4px 12px rgba(139, 92, 246, 0.1);
2480
+ }
2481
+
2482
+ .table-item svg {
2483
+ transition: transform 0.3s ease;
2484
+ }
2485
+ .table-item.active svg {
1971
2486
  color: var(--accent-color);
1972
- border-right: 3px solid var(--accent-color);
2487
+ transform: scale(1.1);
1973
2488
  }
1974
2489
 
1975
2490
  /* Main Content */
@@ -1977,92 +2492,168 @@ var htmlTemplate = `
1977
2492
  flex: 1;
1978
2493
  display: flex;
1979
2494
  flex-direction: column;
1980
- background-color: var(--bg-color);
2495
+ position: relative;
1981
2496
  }
1982
2497
 
1983
2498
  .main-header {
1984
- height: 60px;
1985
- padding: 0 20px;
2499
+ height: 70px;
2500
+ padding: 0 24px;
1986
2501
  border-bottom: 1px solid var(--border-color);
1987
2502
  display: flex;
1988
2503
  align-items: center;
1989
2504
  justify-content: space-between;
1990
2505
  background-color: var(--header-bg);
2506
+ backdrop-filter: blur(12px);
2507
+ -webkit-backdrop-filter: blur(12px);
2508
+ z-index: 10;
1991
2509
  }
1992
2510
 
1993
2511
  .main-header h2 {
1994
2512
  margin: 0;
1995
- font-size: 16px;
1996
- font-weight: 500;
2513
+ font-size: 20px;
2514
+ font-weight: 600;
1997
2515
  color: white;
2516
+ letter-spacing: -0.5px;
2517
+ display: flex;
2518
+ align-items: center;
2519
+ gap: 10px;
1998
2520
  }
1999
2521
 
2000
2522
  .btn {
2001
- background-color: var(--accent-color);
2002
- color: white;
2003
- border: none;
2523
+ background-color: rgba(255, 255, 255, 0.05);
2524
+ border: 1px solid var(--border-color);
2525
+ color: var(--text-main);
2004
2526
  padding: 6px 12px;
2005
2527
  border-radius: 6px;
2006
- font-size: 12px;
2528
+ font-size: 13px;
2007
2529
  font-weight: 500;
2530
+ font-family: 'Outfit', sans-serif;
2008
2531
  cursor: pointer;
2009
- transition: background-color 0.2s ease;
2532
+ transition: all 0.2s ease;
2010
2533
  display: flex;
2011
2534
  align-items: center;
2012
- gap: 6px;
2535
+ gap: 8px;
2013
2536
  }
2014
2537
 
2015
- .btn:hover {
2016
- background-color: var(--accent-hover);
2538
+ .btn:hover:not(:disabled) {
2539
+ background-color: rgba(255, 255, 255, 0.1);
2540
+ }
2541
+
2542
+ .btn:active:not(:disabled) {
2543
+ transform: translateY(1px);
2017
2544
  }
2018
2545
 
2019
2546
  .btn:disabled {
2020
- opacity: 0.5;
2547
+ opacity: 0.4;
2021
2548
  cursor: not-allowed;
2022
2549
  }
2023
2550
 
2024
2551
  .content-area {
2025
2552
  flex: 1;
2026
2553
  overflow: auto;
2027
- padding: 20px;
2554
+ padding: 0;
2555
+ position: relative;
2556
+ z-index: 1;
2557
+ text-align: left;
2028
2558
  }
2029
2559
 
2030
2560
  /* Data Table */
2031
2561
  .data-grid-container {
2032
- background-color: var(--panel-bg);
2562
+ background-color: rgba(24, 24, 27, 0.5);
2033
2563
  border: 1px solid var(--border-color);
2034
- border-radius: 8px;
2035
- overflow: hidden;
2564
+ border-radius: 0;
2565
+ overflow: auto;
2566
+ box-shadow: 0 8px 32px rgba(0,0,0,0.2);
2567
+ backdrop-filter: blur(8px);
2568
+ max-height: 100%;
2569
+ width: 100%;
2570
+ }
2571
+
2572
+ .data-grid-container::-webkit-scrollbar {
2573
+ width: 8px;
2574
+ height: 8px;
2575
+ }
2576
+ .data-grid-container::-webkit-scrollbar-corner {
2577
+ background: transparent;
2578
+ }
2579
+ .data-grid-container::-webkit-scrollbar-thumb {
2580
+ background: #3f3f46;
2581
+ border-radius: 4px;
2036
2582
  }
2037
2583
 
2038
2584
  table {
2039
- width: 100%;
2040
- border-collapse: collapse;
2585
+ width: auto;
2586
+ border-collapse: separate;
2587
+ border-spacing: 0;
2041
2588
  text-align: left;
2042
- font-size: 13px;
2589
+ font-size: 14px;
2590
+ table-layout: auto;
2043
2591
  }
2044
2592
 
2045
2593
  th {
2046
- background-color: rgba(255,255,255,0.02);
2047
- color: var(--text-muted);
2594
+ background-color: rgba(24, 24, 27, 0.95);
2595
+ backdrop-filter: blur(4px);
2596
+ color: var(--text-main);
2048
2597
  font-weight: 500;
2049
- padding: 10px 16px;
2598
+ padding: 12px 20px;
2050
2599
  border-bottom: 1px solid var(--border-color);
2600
+ border-right: 1px solid var(--border-color);
2051
2601
  white-space: nowrap;
2052
2602
  position: sticky;
2053
2603
  top: 0;
2054
2604
  z-index: 10;
2055
2605
  }
2606
+
2607
+
2608
+ th::after {
2609
+ content: '';
2610
+ position: absolute;
2611
+ bottom: -1px; left: 0; right: 0;
2612
+ height: 1px;
2613
+ background: var(--border-color);
2614
+ }
2615
+
2616
+ .col-header {
2617
+ display: flex;
2618
+ flex-direction: column;
2619
+ gap: 4px;
2620
+ }
2621
+
2622
+ .col-name {
2623
+ font-weight: 600;
2624
+ display: flex;
2625
+ align-items: center;
2626
+ gap: 6px;
2627
+ }
2628
+
2629
+ .pk-badge {
2630
+ font-size: 10px;
2631
+ background: rgba(251, 191, 36, 0.2);
2632
+ color: #fbbf24;
2633
+ padding: 2px 6px;
2634
+ border-radius: 4px;
2635
+ font-weight: 600;
2636
+ letter-spacing: 0.5px;
2637
+ }
2638
+
2639
+ .col-type {
2640
+ font-size: 12px;
2641
+ color: var(--text-muted);
2642
+ font-family: monospace;
2643
+ }
2056
2644
 
2057
2645
  td {
2058
- padding: 10px 16px;
2646
+ padding: 12px 20px;
2059
2647
  border-bottom: 1px solid var(--border-color);
2648
+ border-right: 1px solid var(--border-color);
2060
2649
  color: var(--text-main);
2061
2650
  max-width: 300px;
2062
2651
  overflow: hidden;
2063
2652
  text-overflow: ellipsis;
2064
2653
  white-space: nowrap;
2654
+ transition: background-color 0.2s ease;
2065
2655
  }
2656
+
2066
2657
 
2067
2658
  tr:last-child td {
2068
2659
  border-bottom: none;
@@ -2072,11 +2663,17 @@ var htmlTemplate = `
2072
2663
  background-color: var(--row-hover);
2073
2664
  }
2074
2665
 
2075
- .type-number { color: #79c0ff; }
2076
- .type-string { color: #a5d6ff; }
2077
- .type-boolean { color: #ff7b72; }
2078
- .type-date { color: #d2a8ff; }
2079
- .type-null { color: var(--text-muted); font-style: italic; }
2666
+ .type-number { color: var(--type-number); }
2667
+ .type-string { color: var(--type-string); }
2668
+ .type-boolean { color: var(--type-boolean); }
2669
+ .type-date { color: var(--type-date); }
2670
+ .type-json { color: var(--type-json); font-family: monospace; }
2671
+ .type-null { color: var(--type-null); font-style: italic; }
2672
+
2673
+ .cell-value {
2674
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
2675
+ font-size: 13px;
2676
+ }
2080
2677
 
2081
2678
  .empty-state {
2082
2679
  display: flex;
@@ -2084,7 +2681,98 @@ var htmlTemplate = `
2084
2681
  align-items: center;
2085
2682
  justify-content: center;
2086
2683
  height: 100%;
2684
+ width: 100%;
2685
+ color: var(--text-muted);
2686
+ animation: fadeIn 0.5s ease-out;
2687
+ }
2688
+
2689
+ .empty-state svg {
2690
+ margin-bottom: 16px;
2691
+ color: #3f3f46;
2692
+ width: 64px;
2693
+ height: 64px;
2694
+ }
2695
+
2696
+ .empty-state h3 {
2697
+ color: var(--text-main);
2698
+ margin: 0 0 8px 0;
2699
+ font-size: 18px;
2700
+ }
2701
+
2702
+ @keyframes fadeIn {
2703
+ from { opacity: 0; transform: translateY(10px); }
2704
+ to { opacity: 1; transform: translateY(0); }
2705
+ }
2706
+
2707
+ @keyframes spin {
2708
+ from { transform: rotate(0deg); }
2709
+ to { transform: rotate(360deg); }
2710
+ }
2711
+ .spinning {
2712
+ animation: spin 1s linear infinite;
2713
+ }
2714
+
2715
+ /* Pagination */
2716
+ .pagination {
2717
+ display: flex;
2718
+ align-items: center;
2719
+ justify-content: space-between;
2720
+ padding: 16px 24px;
2721
+ background-color: rgba(24, 24, 27, 0.95);
2722
+ border-top: 1px solid var(--border-color);
2723
+ font-size: 14px;
2087
2724
  color: var(--text-muted);
2725
+ position: sticky;
2726
+ bottom: 0;
2727
+ z-index: 5;
2728
+ }
2729
+
2730
+ .pagination-info {
2731
+ display: flex;
2732
+ align-items: center;
2733
+ gap: 16px;
2734
+ }
2735
+
2736
+ .pagination-controls {
2737
+ display: flex;
2738
+ align-items: center;
2739
+ gap: 8px;
2740
+ }
2741
+
2742
+ .pagination-btn {
2743
+ background-color: rgba(255, 255, 255, 0.05);
2744
+ border: 1px solid var(--border-color);
2745
+ color: var(--text-main);
2746
+ padding: 6px 12px;
2747
+ border-radius: 6px;
2748
+ cursor: pointer;
2749
+ font-size: 13px;
2750
+ transition: all 0.2s ease;
2751
+ }
2752
+
2753
+ .pagination-btn:hover:not(:disabled) {
2754
+ background-color: rgba(255, 255, 255, 0.1);
2755
+ }
2756
+
2757
+ .pagination-btn:disabled {
2758
+ opacity: 0.4;
2759
+ cursor: not-allowed;
2760
+ }
2761
+
2762
+ .pagination-btn.active {
2763
+ background-color: var(--accent-color);
2764
+ border-color: var(--accent-color);
2765
+ }
2766
+
2767
+ .page-input {
2768
+ background-color: rgba(255, 255, 255, 0.05);
2769
+ border: 1px solid var(--border-color);
2770
+ color: var(--text-main);
2771
+ padding: 6px 8px;
2772
+ border-radius: 6px;
2773
+ width: 50px;
2774
+ text-align: center;
2775
+ font-size: 13px;
2088
2776
  }
2089
2777
  </style>
2090
2778
  </head>
@@ -2092,7 +2780,7 @@ var htmlTemplate = `
2092
2780
 
2093
2781
  <div class="sidebar">
2094
2782
  <div class="sidebar-header">
2095
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2783
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2096
2784
  <ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
2097
2785
  <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
2098
2786
  <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
@@ -2106,19 +2794,31 @@ var htmlTemplate = `
2106
2794
 
2107
2795
  <div class="main">
2108
2796
  <div class="main-header">
2109
- <h2 id="current-table-name">Select a table</h2>
2797
+ <h2 id="current-table-name">
2798
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color: var(--text-muted);">
2799
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
2800
+ <line x1="3" y1="9" x2="21" y2="9"></line>
2801
+ <line x1="9" y1="21" x2="9" y2="9"></line>
2802
+ </svg>
2803
+ Select a table
2804
+ </h2>
2110
2805
  <button id="refresh-btn" class="btn" disabled>
2111
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2806
+ <svg id="refresh-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2112
2807
  <polyline points="23 4 23 10 17 10"></polyline>
2113
2808
  <polyline points="1 20 1 14 7 14"></polyline>
2114
2809
  <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>
2115
2810
  </svg>
2116
- Refresh Data
2811
+ Refresh
2117
2812
  </button>
2118
2813
  </div>
2119
2814
  <div class="content-area">
2120
2815
  <div id="data-container" class="empty-state">
2121
- <p>Select a table from the sidebar to view data</p>
2816
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
2817
+ <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
2818
+ <polyline points="14 2 14 8 20 8"></polyline>
2819
+ </svg>
2820
+ <h3>No Table Selected</h3>
2821
+ <p>Choose a table from the sidebar to view its data</p>
2122
2822
  </div>
2123
2823
  </div>
2124
2824
  </div>
@@ -2126,21 +2826,38 @@ var htmlTemplate = `
2126
2826
  <script>
2127
2827
  let currentTable = null;
2128
2828
  let tablesData = [];
2829
+ let tableSchemas = {};
2830
+ let currentPage = 1;
2831
+ let totalPages = 1;
2832
+ let totalRecords = 0;
2833
+ let pageSize = 50;
2129
2834
 
2130
2835
  // Format values for the data grid
2131
2836
  function formatValue(val) {
2132
- if (val === null || val === undefined) return '<span class="type-null">null</span>';
2133
- if (typeof val === 'number') return \`<span class="type-number">\${val}</span>\`;
2134
- if (typeof val === 'boolean') return \`<span class="type-boolean">\${val}</span>\`;
2135
- if (val instanceof Date || (typeof val === 'string' && val.match(/^\\d{4}-\\d{2}-\\d{2}T/))) {
2136
- return \`<span class="type-date">\${new Date(val).toLocaleString()}</span>\`;
2837
+ if (val === null || val === undefined) return 'null';
2838
+
2839
+ if (typeof val === 'object') {
2840
+ if (val instanceof Date) {
2841
+ return val.toISOString();
2842
+ }
2843
+ // Handle JSON objects
2844
+ const safeJson = JSON.stringify(val)
2845
+ .replace(/&/g, "&amp;")
2846
+ .replace(/</g, "&lt;")
2847
+ .replace(/>/g, "&gt;");
2848
+ return safeJson;
2849
+ }
2850
+
2851
+ if (typeof val === 'string') {
2852
+ // Escape HTML to prevent XSS
2853
+ const safeStr = String(val)
2854
+ .replace(/&/g, "&amp;")
2855
+ .replace(/</g, "&lt;")
2856
+ .replace(/>/g, "&gt;");
2857
+ return safeStr;
2137
2858
  }
2138
- // Escape HTML to prevent XSS
2139
- const safeStr = String(val)
2140
- .replace(/&/g, "&amp;")
2141
- .replace(/</g, "&lt;")
2142
- .replace(/>/g, "&gt;");
2143
- return \`<span class="type-string">\${safeStr}</span>\`;
2859
+
2860
+ return String(val);
2144
2861
  }
2145
2862
 
2146
2863
  async function loadTables() {
@@ -2150,12 +2867,14 @@ var htmlTemplate = `
2150
2867
 
2151
2868
  const list = document.getElementById('table-list');
2152
2869
  list.innerHTML = '';
2870
+ tableSchemas = {};
2153
2871
 
2154
2872
  tablesData.forEach(t => {
2873
+ tableSchemas[t.name] = t;
2155
2874
  const li = document.createElement('li');
2156
2875
  li.className = 'table-item';
2157
2876
  li.innerHTML = \`
2158
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2877
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2159
2878
  <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
2160
2879
  <line x1="3" y1="9" x2="21" y2="9"></line>
2161
2880
  <line x1="9" y1="21" x2="9" y2="9"></line>
@@ -2170,8 +2889,9 @@ var htmlTemplate = `
2170
2889
  }
2171
2890
  }
2172
2891
 
2173
- async function selectTable(name) {
2892
+ async function selectTable(name, page = 1) {
2174
2893
  currentTable = name;
2894
+ currentPage = page;
2175
2895
 
2176
2896
  // Update UI active state
2177
2897
  document.querySelectorAll('.table-item').forEach(el => {
@@ -2179,60 +2899,169 @@ var htmlTemplate = `
2179
2899
  else el.classList.remove('active');
2180
2900
  });
2181
2901
 
2182
- document.getElementById('current-table-name').textContent = name;
2902
+ document.getElementById('current-table-name').innerHTML = \`
2903
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color: var(--accent-color);">
2904
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
2905
+ <line x1="3" y1="9" x2="21" y2="9"></line>
2906
+ <line x1="9" y1="21" x2="9" y2="9"></line>
2907
+ </svg>
2908
+ \${name}
2909
+ \`;
2183
2910
  document.getElementById('refresh-btn').disabled = false;
2184
2911
 
2185
2912
  const container = document.getElementById('data-container');
2186
- container.innerHTML = '<div class="empty-state">Loading data...</div>';
2187
- container.className = '';
2913
+ container.innerHTML = \`
2914
+ <div class="empty-state">
2915
+ <svg class="spinning" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2916
+ <line x1="12" y1="2" x2="12" y2="6"></line>
2917
+ <line x1="12" y1="18" x2="12" y2="22"></line>
2918
+ <line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line>
2919
+ <line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line>
2920
+ <line x1="2" y1="12" x2="6" y2="12"></line>
2921
+ <line x1="18" y1="12" x2="22" y2="12"></line>
2922
+ <line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line>
2923
+ <line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line>
2924
+ </svg>
2925
+ <p>Loading data...</p>
2926
+ </div>
2927
+ \`;
2188
2928
 
2189
2929
  try {
2190
- const res = await fetch(\`/api/tables/\${name}/data\`);
2930
+ const res = await fetch(\`/api/tables/\${name}/data?page=\${page}&limit=\${pageSize}\`);
2191
2931
  if (!res.ok) throw new Error(await res.text());
2192
- const rows = await res.json();
2932
+ const response = await res.json();
2933
+
2934
+ const rows = response.data || response;
2935
+ totalRecords = response.total || 0;
2936
+ totalPages = response.totalPages || 1;
2937
+
2938
+ const tableSchema = tableSchemas[name];
2193
2939
 
2194
2940
  if (rows.length === 0) {
2195
- container.innerHTML = '<div class="empty-state">No data in this table</div>';
2941
+ container.innerHTML = \`
2942
+ <div class="empty-state">
2943
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
2944
+ <circle cx="12" cy="12" r="10"></circle>
2945
+ <line x1="12" y1="8" x2="12" y2="12"></line>
2946
+ <line x1="12" y1="16" x2="12.01" y2="16"></line>
2947
+ </svg>
2948
+ <h3>Table is Empty</h3>
2949
+ <p>No records found in "\${name}"</p>
2950
+ </div>
2951
+ \`;
2196
2952
  return;
2197
2953
  }
2198
2954
 
2199
- const columns = Object.keys(rows[0]);
2955
+ // Use all columns from the actual data to ensure foreign keys and all data are shown
2956
+ let columns = [];
2957
+ const allKeys = new Set();
2958
+ rows.forEach(row => {
2959
+ Object.keys(row).forEach(key => allKeys.add(key));
2960
+ });
2961
+
2962
+ // Try to get type info from schema if available
2963
+ const schemaColMap = {};
2964
+ if (tableSchema && tableSchema.columns && tableSchema.columns.length > 0) {
2965
+ tableSchema.columns.forEach(col => {
2966
+ schemaColMap[col.name] = col;
2967
+ });
2968
+ }
2969
+
2970
+ columns = Array.from(allKeys).map(key => ({
2971
+ name: key,
2972
+ type: schemaColMap[key] ? schemaColMap[key].type : typeof rows[0][key],
2973
+ primaryKey: schemaColMap[key] ? schemaColMap[key].primaryKey : false
2974
+ }));
2200
2975
 
2201
2976
  let html = '<div class="data-grid-container"><table><thead><tr>';
2202
2977
  columns.forEach(col => {
2203
- html += \`<th>\${col}</th>\`;
2978
+ html += \`
2979
+ <th>
2980
+ <div class="col-header">
2981
+ <div class="col-name">
2982
+ \${col.name}
2983
+ \${col.primaryKey ? '<span class="pk-badge">PK</span>' : ''}
2984
+ </div>
2985
+ <div class="col-type">\${col.type}</div>
2986
+ </div>
2987
+ </th>
2988
+ \`;
2204
2989
  });
2205
2990
  html += '</tr></thead><tbody>';
2206
2991
 
2207
2992
  rows.forEach(row => {
2208
2993
  html += '<tr>';
2209
2994
  columns.forEach(col => {
2210
- html += \`<td>\${formatValue(row[col])}</td>\`;
2995
+ html += \`<td>\${formatValue(row[col.name])}</td>\`;
2211
2996
  });
2212
2997
  html += '</tr>';
2213
2998
  });
2214
2999
 
2215
- html += '</tbody></table></div>';
3000
+ html += '</tbody></table>';
3001
+
3002
+ // Add pagination
3003
+ html += renderPagination();
3004
+
3005
+ html += '</div>';
2216
3006
  container.innerHTML = html;
2217
3007
 
2218
3008
  } catch (e) {
2219
- container.innerHTML = \`<div class="empty-state" style="color: #ff7b72;">Error: \${e.message}</div>\`;
3009
+ container.innerHTML = \`
3010
+ <div class="empty-state">
3011
+ <svg viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
3012
+ <circle cx="12" cy="12" r="10"></circle>
3013
+ <line x1="12" y1="8" x2="12" y2="12"></line>
3014
+ <line x1="12" y1="16" x2="12.01" y2="16"></line>
3015
+ </svg>
3016
+ <h3 style="color: #ef4444;">Error Loading Data</h3>
3017
+ <p>\${e.message}</p>
3018
+ </div>
3019
+ \`;
2220
3020
  }
2221
3021
  }
2222
3022
 
3023
+ function renderPagination() {
3024
+ const startRecord = (currentPage - 1) * pageSize + 1;
3025
+ const endRecord = Math.min(currentPage * pageSize, totalRecords);
3026
+
3027
+ let html = '<div class="pagination">';
3028
+ html += '<div class="pagination-info">';
3029
+ html += \`<span>Total: \${totalRecords} records</span>\`;
3030
+ html += \`<span>Page \${currentPage} of \${totalPages}</span>\`;
3031
+ html += \`<span>Showing \${startRecord}-\${endRecord}</span>\`;
3032
+ html += '</div>';
3033
+
3034
+ html += '<div class="pagination-controls">';
3035
+
3036
+ // Previous button
3037
+ html += \`<button class="pagination-btn" onclick="changePage(\${currentPage - 1})" \${currentPage === 1 ? 'disabled' : ''}>Previous</button>\`;
3038
+
3039
+ // Next button
3040
+ html += \`<button class="pagination-btn" onclick="changePage(\${currentPage + 1})" \${currentPage === totalPages ? 'disabled' : ''}>Next</button>\`;
3041
+
3042
+ html += '</div>';
3043
+ html += '</div>';
3044
+
3045
+ return html;
3046
+ }
3047
+
3048
+ function changePage(page) {
3049
+ if (page < 1 || page > totalPages) return;
3050
+ selectTable(currentTable, page);
3051
+ }
3052
+
2223
3053
  document.getElementById('refresh-btn').addEventListener('click', async () => {
2224
3054
  if (!currentTable) return;
2225
3055
 
2226
- const btn = document.getElementById('refresh-btn');
2227
- const originalText = btn.innerHTML;
2228
- btn.innerHTML = 'Refreshing...';
3056
+ const icon = document.getElementById('refresh-icon');
3057
+ icon.classList.add('spinning');
2229
3058
 
2230
3059
  try {
2231
3060
  await selectTable(currentTable);
2232
3061
  } catch (e) {
2233
3062
  console.error("Failed to refresh data", e);
2234
3063
  } finally {
2235
- setTimeout(() => { btn.innerHTML = originalText; }, 300);
3064
+ setTimeout(() => { icon.classList.remove('spinning'); }, 500);
2236
3065
  }
2237
3066
  });
2238
3067
 
@@ -2251,7 +3080,7 @@ async function runTusky(config) {
2251
3080
  for (const s of schemas) {
2252
3081
  schemaObj[s.exportName] = s.table;
2253
3082
  }
2254
- const db = createDB({ url: config.dbUrl, schema: schemaObj });
3083
+ const db = bungres({ url: config.dbUrl, schema: schemaObj });
2255
3084
  console.log("=========================================");
2256
3085
  console.log("\uD83D\uDC18 Welcome to Bungres REPL (Tusky)");
2257
3086
  console.log("=========================================");
@@ -2308,6 +3137,46 @@ Example query: await db.select().from(users)`);
2308
3137
  });
2309
3138
  }
2310
3139
 
3140
+ // src/config.ts
3141
+ import { join as join6, resolve as resolve7 } from "path";
3142
+ var CONFIG_FILES = [
3143
+ "bungres.config.ts",
3144
+ "bungres.config.js",
3145
+ "bungres.config.mts",
3146
+ "bungres.config.mjs"
3147
+ ];
3148
+ async function loadConfig(cwd = process.cwd()) {
3149
+ let userConfig = {};
3150
+ for (const file of CONFIG_FILES) {
3151
+ const configPath = join6(cwd, file);
3152
+ if (await Bun.file(configPath).exists()) {
3153
+ const mod = await import(resolve7(configPath));
3154
+ userConfig = mod.default ?? mod;
3155
+ break;
3156
+ }
3157
+ }
3158
+ const dbUrl = userConfig.dbCredentials?.url ?? Bun.env.DATABASE_URL ?? Bun.env.POSTGRES_URL ?? "";
3159
+ if (!dbUrl) {
3160
+ console.error(`bungres: No database URL found.
3161
+ Add dbCredentials.url to bungres.config.ts or set DATABASE_URL.`);
3162
+ process.exit(1);
3163
+ }
3164
+ const out = userConfig.out ?? "./migrations";
3165
+ return {
3166
+ schema: userConfig.schema ?? "src/db/schema/**/*.ts",
3167
+ seed: userConfig.seed ?? "src/db/seed.ts",
3168
+ out,
3169
+ dbUrl,
3170
+ dbSchema: userConfig.dbSchema ?? "public",
3171
+ migrationsTable: userConfig.migrations?.table ?? "__bungres_migrations",
3172
+ migrationsSchema: userConfig.migrations?.schema ?? "bungres",
3173
+ breakpoints: userConfig.breakpoints ?? true,
3174
+ strict: userConfig.strict ?? false,
3175
+ outDir: "./src/db/generated",
3176
+ verbose: userConfig.verbose ?? false
3177
+ };
3178
+ }
3179
+
2311
3180
  // src/ensure-db.ts
2312
3181
  async function ensureDatabase2(dbUrl) {
2313
3182
  let sql2;
@@ -2347,6 +3216,7 @@ ${colorize("Usage:", "yellow")}
2347
3216
  bungres <command> [options]
2348
3217
 
2349
3218
  ${colorize("Commands:", "yellow")}
3219
+ init Initialize bungres project with config file and db folder structure
2350
3220
  generate Generate SQL migration files from your schema definitions
2351
3221
  migrate Run pending migration files against the database
2352
3222
  push Apply schema directly to the DB (no migration files, dev mode)
@@ -2367,6 +3237,7 @@ ${colorize("Options:", "yellow")}
2367
3237
  --help Show this help
2368
3238
 
2369
3239
  ${colorize("Examples:", "yellow")}
3240
+ bungres init
2370
3241
  bungres generate
2371
3242
  bungres migrate
2372
3243
  bungres push
@@ -2391,6 +3262,10 @@ async function main() {
2391
3262
  }
2392
3263
  const command = args[0];
2393
3264
  const flags = parseFlags(args.slice(1));
3265
+ if (command === "init") {
3266
+ await runInit();
3267
+ process.exit(0);
3268
+ }
2394
3269
  const config2 = await loadConfig(process.cwd());
2395
3270
  if (flags.verbose)
2396
3271
  config2.verbose = true;