@bungres/kit 1.1.0 → 1.1.2

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/index.js CHANGED
@@ -168,14 +168,25 @@ var CONFIG_FILES = [
168
168
  "bungres.config.mts",
169
169
  "bungres.config.mjs"
170
170
  ];
171
- async function loadConfig(cwd = process.cwd()) {
171
+ async function loadConfig(cwd = process.cwd(), explicitConfigPath) {
172
172
  let userConfig = {};
173
- for (const file of CONFIG_FILES) {
174
- const configPath = join(cwd, file);
175
- if (await Bun.file(configPath).exists()) {
176
- const mod = await import(resolve(configPath));
173
+ if (explicitConfigPath) {
174
+ const fullPath = resolve(cwd, explicitConfigPath);
175
+ if (await Bun.file(fullPath).exists()) {
176
+ const mod = await import(fullPath);
177
177
  userConfig = mod.default ?? mod;
178
- break;
178
+ } else {
179
+ console.error(`bungres: Config file not found at ${fullPath}`);
180
+ process.exit(1);
181
+ }
182
+ } else {
183
+ for (const file of CONFIG_FILES) {
184
+ const configPath = join(cwd, file);
185
+ if (await Bun.file(configPath).exists()) {
186
+ const mod = await import(resolve(configPath));
187
+ userConfig = mod.default ?? mod;
188
+ break;
189
+ }
179
190
  }
180
191
  }
181
192
  const dbUrl = userConfig.dbCredentials?.url ?? Bun.env.DATABASE_URL ?? Bun.env.POSTGRES_URL ?? "";
@@ -256,7 +267,7 @@ function createTableFactory(casing) {
256
267
  const tableObj = {
257
268
  [TableConfigSymbol]: {
258
269
  name,
259
- schema,
270
+ ...schema ? { schema } : {},
260
271
  columns: columnConfigs,
261
272
  indexes,
262
273
  checks,
@@ -268,10 +279,10 @@ function createTableFactory(casing) {
268
279
  return Object.assign(tableObj, columnConfigs);
269
280
  };
270
281
  }
271
- var table = createTableFactory("snake");
272
- var snakeCase = { table: createTableFactory("snake") };
273
- var camelCase = { table: createTableFactory("camel") };
274
- var noCasing = { table: createTableFactory("none") };
282
+ var pgTable = createTableFactory("snake");
283
+ var snakeCase = { pgTable: createTableFactory("snake") };
284
+ var camelCase = { pgTable: createTableFactory("camel") };
285
+ var noCasing = { pgTable: createTableFactory("none") };
275
286
  function buildColumn(dataType, nameOrOpts, opts) {
276
287
  let name = "";
277
288
  let options = opts;
@@ -298,8 +309,8 @@ function buildColumn(dataType, nameOrOpts, opts) {
298
309
  ...options?.check !== undefined ? { check: options.check } : {}
299
310
  };
300
311
  return Object.assign(config, {
301
- as(alias) {
302
- return Object.assign({}, this, { alias });
312
+ as(alias2) {
313
+ return Object.assign({}, this, { alias: alias2 });
303
314
  },
304
315
  array() {
305
316
  return Object.assign({}, this, { dataType: `${this.dataType}[]` });
@@ -336,6 +347,41 @@ var textArray = col("text[]");
336
347
  var integerArray = col("integer[]");
337
348
  var varcharArray = col("varchar[]");
338
349
  var uuidArray = col("uuid[]");
350
+ function shiftParams(sql, paramsLength, offset) {
351
+ if (paramsLength === 0)
352
+ return sql;
353
+ let result = "";
354
+ let inString = false;
355
+ let inIdent = false;
356
+ let i = 0;
357
+ while (i < sql.length) {
358
+ if (sql[i] === "'" && !inIdent) {
359
+ inString = !inString;
360
+ result += sql[i];
361
+ i++;
362
+ } else if (sql[i] === '"' && !inString) {
363
+ inIdent = !inIdent;
364
+ result += sql[i];
365
+ i++;
366
+ } else if (!inString && !inIdent && sql[i] === "$") {
367
+ const match = sql.slice(i).match(/^\$(\d+)/);
368
+ if (match) {
369
+ const num = parseInt(match[1], 10);
370
+ if (num >= 1 && num <= paramsLength) {
371
+ result += `$${num + offset}`;
372
+ i += match[0].length;
373
+ continue;
374
+ }
375
+ }
376
+ result += sql[i];
377
+ i++;
378
+ } else {
379
+ result += sql[i];
380
+ i++;
381
+ }
382
+ }
383
+ return result;
384
+ }
339
385
  function sql(strings, ...values) {
340
386
  let query = "";
341
387
  const params = [];
@@ -345,7 +391,7 @@ function sql(strings, ...values) {
345
391
  const val = values[i];
346
392
  if (isSQLChunk(val)) {
347
393
  const offset = params.length;
348
- query += val.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
394
+ query += shiftParams(val.sql, val.params.length, offset);
349
395
  params.push(...val.params);
350
396
  } else {
351
397
  params.push(val);
@@ -363,7 +409,7 @@ function sqlJoin(chunks, separator = ", ") {
363
409
  const parts = [];
364
410
  for (const chunk of chunks) {
365
411
  const offset = params.length;
366
- parts.push(chunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`));
412
+ parts.push(shiftParams(chunk.sql, chunk.params.length, offset));
367
413
  params.push(...chunk.params);
368
414
  }
369
415
  return { sql: parts.join(separator), params };
@@ -371,6 +417,19 @@ function sqlJoin(chunks, separator = ", ") {
371
417
  function rawSql(query) {
372
418
  return { sql: query, params: [] };
373
419
  }
420
+ function toPgArray(val) {
421
+ const formatted = val.map((item) => {
422
+ if (item === null || item === undefined)
423
+ return "NULL";
424
+ if (typeof item === "number" || typeof item === "boolean")
425
+ return String(item);
426
+ if (Array.isArray(item))
427
+ return toPgArray(item);
428
+ const str = String(item).replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
429
+ return `"${str}"`;
430
+ });
431
+ return `{${formatted.join(",")}}`;
432
+ }
374
433
  function colName(c) {
375
434
  if (typeof c === "string")
376
435
  return `"${c}"`;
@@ -428,18 +487,26 @@ var ilike = (column, pattern) => sql`${rawSql(colName(column))} ILIKE ${pattern}
428
487
  var isNull = (column) => rawSql(`${colName(column)} IS NULL`);
429
488
  var isNotNull = (column) => rawSql(`${colName(column)} IS NOT NULL`);
430
489
  var inArray = (column, values) => {
431
- if (values.length === 0)
490
+ if (typeof values === "object" && values !== null && "toSQL" in values) {
491
+ return sql`${rawSql(colName(column))} IN (${values.toSQL()})`;
492
+ }
493
+ const arr = values;
494
+ if (arr.length === 0)
432
495
  return rawSql("FALSE");
433
- const params = values;
434
- const placeholders = params.map((_, i) => `$${i + 1}`).join(", ");
435
- return { sql: `${colName(column)} = ANY(ARRAY[${placeholders}])`, params };
496
+ const params = arr;
497
+ const placeholders = arr.map((_, i) => `$${i + 1}`).join(", ");
498
+ return { sql: `${colName(column)} IN (${placeholders})`, params };
436
499
  };
437
500
  var notInArray = (column, values) => {
438
- if (values.length === 0)
501
+ if (typeof values === "object" && values !== null && "toSQL" in values) {
502
+ return sql`${rawSql(colName(column))} NOT IN (${values.toSQL()})`;
503
+ }
504
+ const arr = values;
505
+ if (arr.length === 0)
439
506
  return rawSql("TRUE");
440
- const params = values;
441
- const placeholders = params.map((_, i) => `$${i + 1}`).join(", ");
442
- return { sql: `${colName(column)} != ALL(ARRAY[${placeholders}])`, params };
507
+ const params = arr;
508
+ const placeholders = arr.map((_, i) => `$${i + 1}`).join(", ");
509
+ return { sql: `${colName(column)} NOT IN (${placeholders})`, params };
443
510
  };
444
511
  var between = (column, min, max) => sql`${rawSql(colName(column))} BETWEEN ${min} AND ${max}`;
445
512
  var and = (...conditions) => sqlJoin(conditions, " AND ");
@@ -453,29 +520,29 @@ var not = (condition) => ({
453
520
  });
454
521
  var asc = (column) => sql`${rawSql(colName(column))} ASC`;
455
522
  var desc = (column) => sql`${rawSql(colName(column))} DESC`;
456
- function parseWhereObject(tableConfig, whereObj) {
523
+ function parseWhereObject(tableConfig, whereObj, alias2) {
457
524
  const conditions = [];
458
525
  for (const [key, val] of Object.entries(whereObj)) {
459
526
  if (val === undefined)
460
527
  continue;
461
528
  if (key === "OR") {
462
- const orConditions = val.map((o) => parseWhereObject(tableConfig, o));
529
+ const orConditions = val.map((o) => parseWhereObject(tableConfig, o, alias2));
463
530
  if (orConditions.length > 0)
464
531
  conditions.push(or(...orConditions));
465
532
  continue;
466
533
  }
467
534
  if (key === "AND") {
468
- const andConditions = val.map((o) => parseWhereObject(tableConfig, o));
535
+ const andConditions = val.map((o) => parseWhereObject(tableConfig, o, alias2));
469
536
  if (andConditions.length > 0)
470
537
  conditions.push(and(...andConditions));
471
538
  continue;
472
539
  }
473
540
  if (key === "NOT") {
474
- conditions.push(not(parseWhereObject(tableConfig, val)));
541
+ conditions.push(not(parseWhereObject(tableConfig, val, alias2)));
475
542
  continue;
476
543
  }
477
544
  const colConfig = tableConfig.columns[key];
478
- const columnArg = colConfig ?? key;
545
+ const columnArg = colConfig ? alias2 ? { ...colConfig, tableName: alias2 } : colConfig : alias2 ? { name: key, tableName: alias2 } : key;
479
546
  if (val !== null && typeof val === "object" && !Array.isArray(val) && !(val instanceof Date) && !(val instanceof Uint8Array)) {
480
547
  const opVal = val;
481
548
  if (opVal.eq !== undefined)
@@ -517,13 +584,13 @@ function parseWhereObject(tableConfig, whereObj) {
517
584
  }
518
585
  return and(...conditions);
519
586
  }
520
- function parseOrderByObject(tableConfig, orderByObj) {
587
+ function parseOrderByObject(tableConfig, orderByObj, alias2) {
521
588
  const parts = [];
522
589
  for (const [key, dir] of Object.entries(orderByObj)) {
523
590
  if (dir === undefined)
524
591
  continue;
525
592
  const colConfig = tableConfig.columns[key];
526
- const columnArg = colConfig ?? key;
593
+ const columnArg = colConfig ? alias2 ? { ...colConfig, tableName: alias2 } : colConfig : alias2 ? { name: key, tableName: alias2 } : key;
527
594
  if (dir === "asc")
528
595
  parts.push(asc(columnArg));
529
596
  else if (dir === "desc")
@@ -538,8 +605,8 @@ class DeleteBuilder {
538
605
  _returning;
539
606
  _comment;
540
607
  _with = [];
541
- constructor(table2, executor) {
542
- this._table = table2;
608
+ constructor(table, executor) {
609
+ this._table = table;
543
610
  this._executor = executor;
544
611
  }
545
612
  then(onfulfilled, onrejected) {
@@ -607,8 +674,8 @@ class InsertBuilder {
607
674
  _returning;
608
675
  _comment;
609
676
  _with = [];
610
- constructor(table2, executor) {
611
- this._table = table2;
677
+ constructor(table, executor) {
678
+ this._table = table;
612
679
  this._executor = executor;
613
680
  }
614
681
  then(onfulfilled, onrejected) {
@@ -691,14 +758,7 @@ class InsertBuilder {
691
758
  if (colType === "json" || colType === "jsonb") {
692
759
  params.push(val);
693
760
  } else if (Array.isArray(val)) {
694
- const pgArray = "{" + val.map((item) => {
695
- if (item === null || item === undefined)
696
- return "NULL";
697
- if (typeof item === "string")
698
- return '"' + item.replace(/"/g, "\\\"") + '"';
699
- return typeof item === "object" ? '"' + JSON.stringify(item).replace(/"/g, "\\\"") + '"' : String(item);
700
- }).join(",") + "}";
701
- params.push(pgArray);
761
+ params.push(toPgArray(val));
702
762
  } else {
703
763
  params.push(JSON.stringify(val));
704
764
  }
@@ -749,14 +809,7 @@ class InsertBuilder {
749
809
  if (colType === "json" || colType === "jsonb") {
750
810
  params.push(value);
751
811
  } else if (Array.isArray(value)) {
752
- const pgArray = "{" + value.map((item) => {
753
- if (item === null || item === undefined)
754
- return "NULL";
755
- if (typeof item === "string")
756
- return '"' + item.replace(/"/g, "\\\"") + '"';
757
- return typeof item === "object" ? '"' + JSON.stringify(item).replace(/"/g, "\\\"") + '"' : String(item);
758
- }).join(",") + "}";
759
- params.push(pgArray);
812
+ params.push(toPgArray(value));
760
813
  } else {
761
814
  params.push(JSON.stringify(value));
762
815
  }
@@ -789,22 +842,29 @@ class SelectBuilder {
789
842
  _select;
790
843
  _selection;
791
844
  _joins = [];
845
+ _isNestedOutput = false;
792
846
  _with = [];
793
847
  _setOperations = [];
794
848
  _comment;
795
- constructor(table2, executor, selection) {
796
- this._table = table2;
849
+ constructor(table, executor, selection) {
850
+ this._table = table;
797
851
  this._executor = executor;
798
852
  this._selection = selection;
799
853
  }
800
854
  then(onfulfilled, onrejected) {
801
- return this._executor.execute(this).then(onfulfilled, onrejected);
855
+ return this._executor.execute(this).then((rows) => {
856
+ if (this._isNestedOutput) {
857
+ rows = rows.map((r) => typeof r._nested_data === "string" ? JSON.parse(r._nested_data) : r._nested_data);
858
+ }
859
+ return onfulfilled ? onfulfilled(rows) : rows;
860
+ }, onrejected);
802
861
  }
803
862
  async single() {
804
863
  if (this._limit === undefined) {
805
864
  this.limit(1);
806
865
  }
807
- return this._executor.executeSingle(this);
866
+ const rows = await this.then();
867
+ return rows[0] ?? null;
808
868
  }
809
869
  select(...columns) {
810
870
  this._select = columns;
@@ -866,40 +926,82 @@ class SelectBuilder {
866
926
  this._offset = n;
867
927
  return this;
868
928
  }
929
+ as(aliasName) {
930
+ const columns = {};
931
+ let fields = [];
932
+ if (this._selection) {
933
+ fields = Object.keys(this._selection);
934
+ } else if (this._select && this._select.length > 0) {
935
+ fields = this._select;
936
+ } else if (!(("alias" in this._table) && ("query" in this._table))) {
937
+ fields = Object.keys(getTableConfig(this._table).columns);
938
+ }
939
+ for (const f of fields) {
940
+ const key = typeof f === "string" ? f : f.name || f.alias;
941
+ const dataType = typeof f !== "string" && f.dataType ? f.dataType : "any";
942
+ if (key)
943
+ columns[key] = { name: key, tableName: aliasName, dataType };
944
+ }
945
+ const sqObj = { ...columns };
946
+ const TableConfigSymbol2 = Symbol.for("BungresTableConfig");
947
+ sqObj[TableConfigSymbol2] = {
948
+ name: aliasName,
949
+ qualifiedName: `"${aliasName}"`,
950
+ columns,
951
+ isSubquery: true,
952
+ builder: this
953
+ };
954
+ return sqObj;
955
+ }
869
956
  join(rawClause) {
870
- this._joins.push(rawClause);
957
+ this._joins.push({ chunk: rawSql(rawClause) });
871
958
  return this;
872
959
  }
873
- innerJoin(table2, condition) {
874
- this._joins.push(sql`INNER JOIN ${rawSql(getTableConfig(table2).qualifiedName)} ON ${condition}`);
960
+ _buildJoin(type, table, condition) {
961
+ const TableConfigSymbol2 = Symbol.for("BungresTableConfig");
962
+ const cfg = table[TableConfigSymbol2];
963
+ if (cfg && cfg.isSubquery) {
964
+ const sqChunk = cfg.builder.toSQL();
965
+ return {
966
+ table,
967
+ chunk: sql`${rawSql(type)} JOIN (${sqChunk}) AS "${rawSql(cfg.name)}" ON ${condition}`
968
+ };
969
+ }
970
+ return {
971
+ table,
972
+ chunk: sql`${rawSql(type)} JOIN ${rawSql(cfg.qualifiedName)} ON ${condition}`
973
+ };
974
+ }
975
+ innerJoin(table, condition) {
976
+ this._joins.push(this._buildJoin("INNER", table, condition));
875
977
  return this;
876
978
  }
877
- leftJoin(table2, condition) {
878
- this._joins.push(sql`LEFT JOIN ${rawSql(getTableConfig(table2).qualifiedName)} ON ${condition}`);
979
+ leftJoin(table, condition) {
980
+ this._joins.push(this._buildJoin("LEFT", table, condition));
879
981
  return this;
880
982
  }
881
- rightJoin(table2, condition) {
882
- this._joins.push(sql`RIGHT JOIN ${rawSql(getTableConfig(table2).qualifiedName)} ON ${condition}`);
983
+ rightJoin(table, condition) {
984
+ this._joins.push(this._buildJoin("RIGHT", table, condition));
883
985
  return this;
884
986
  }
885
- fullJoin(table2, condition) {
886
- this._joins.push(sql`FULL JOIN ${rawSql(getTableConfig(table2).qualifiedName)} ON ${condition}`);
987
+ fullJoin(table, condition) {
988
+ this._joins.push(this._buildJoin("FULL", table, condition));
887
989
  return this;
888
990
  }
889
991
  _buildSelection(selection, params) {
890
992
  const parts = [];
891
- for (const [alias, col2] of Object.entries(selection)) {
993
+ for (const [alias2, col2] of Object.entries(selection)) {
892
994
  if (typeof col2 === "object" && col2 !== null) {
893
995
  if ("sql" in col2 && "params" in col2) {
894
996
  const chunk = col2;
895
997
  const offset = params.length;
896
998
  params.push(...chunk.params);
897
- parts.push(`'${alias}', ${chunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`)}`);
999
+ parts.push(`'${alias2}', ${chunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`)}`);
898
1000
  } else if ("name" in col2 && "dataType" in col2) {
899
1001
  const c = col2;
900
- parts.push(`'${alias}', ${c.tableName ? c.tableName + "." : ""}"${c.name}"`);
1002
+ parts.push(`'${alias2}', ${c.tableName ? c.tableName + "." : ""}"${c.name}"`);
901
1003
  } else {
902
- parts.push(`'${alias}', json_build_object(${this._buildSelection(col2, params)})`);
1004
+ parts.push(`'${alias2}', json_build_object(${this._buildSelection(col2, params)})`);
903
1005
  }
904
1006
  }
905
1007
  }
@@ -908,35 +1010,59 @@ class SelectBuilder {
908
1010
  toSQL() {
909
1011
  let cols = "";
910
1012
  const params = [];
1013
+ const isCte = "alias" in this._table && "query" in this._table;
1014
+ const qName = isCte ? `"${this._table.alias}"` : getTableConfig(this._table).qualifiedName;
911
1015
  if (this._selection) {
912
- cols = Object.entries(this._selection).map(([alias, col2]) => {
1016
+ cols = Object.entries(this._selection).map(([alias2, col2]) => {
913
1017
  if (typeof col2 === "object" && col2 !== null && "sql" in col2 && "params" in col2) {
914
1018
  const chunk = col2;
915
1019
  const offset = params.length;
916
1020
  params.push(...chunk.params);
917
- return `${chunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`)} AS "${alias}"`;
1021
+ return `${chunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`)} AS "${alias2}"`;
918
1022
  } else if (typeof col2 === "object" && col2 !== null && "name" in col2 && "dataType" in col2) {
919
1023
  const c = col2;
920
- return `${c.tableName ? c.tableName + "." : ""}"${c.name}" AS "${alias}"`;
1024
+ return `${c.tableName ? c.tableName + "." : ""}"${c.name}" AS "${alias2}"`;
921
1025
  } else {
922
- return `json_build_object(${this._buildSelection(col2, params)}) AS "${alias}"`;
1026
+ return `json_build_object(${this._buildSelection(col2, params)}) AS "${alias2}"`;
923
1027
  }
924
1028
  }).join(", ");
925
1029
  } else if (this._select && this._select.length > 0) {
926
- const qName = getTableConfig(this._table).qualifiedName;
927
1030
  cols = this._select.map((c) => {
928
1031
  if (typeof c === "string") {
929
- return `${qName}."${getTableConfig(this._table).columns[c]?.name ?? c}" AS "${c}"`;
1032
+ const colName2 = isCte ? c : getTableConfig(this._table).columns[c]?.name ?? c;
1033
+ return `${qName}."${colName2}" AS "${c}"`;
930
1034
  }
931
1035
  return `${c.tableName ? c.tableName + "." : ""}"${c.name}" AS "${c.alias || c.name}"`;
932
1036
  }).join(", ");
933
1037
  } else {
934
- const qName = getTableConfig(this._table).qualifiedName;
935
- const colsKeys = Object.keys(getTableConfig(this._table).columns);
936
- if (colsKeys.length === 0) {
1038
+ if (isCte) {
937
1039
  cols = "*";
938
1040
  } else {
939
- cols = colsKeys.map((c) => `${qName}."${getTableConfig(this._table).columns[c].name}" AS "${c}"`).join(", ");
1041
+ const hasJoinedTables = this._joins.some((j) => j.table);
1042
+ if (hasJoinedTables) {
1043
+ this._isNestedOutput = true;
1044
+ const rootCfg = getTableConfig(this._table);
1045
+ const tablesToSelect = [{ alias: rootCfg.name, config: rootCfg }];
1046
+ for (const join2 of this._joins) {
1047
+ if (join2.table) {
1048
+ const cfg = getTableConfig(join2.table);
1049
+ tablesToSelect.push({ alias: cfg.name, config: cfg });
1050
+ }
1051
+ }
1052
+ cols = `json_build_object(${tablesToSelect.map((t) => {
1053
+ const innerFields = Object.keys(t.config.columns).map((c) => {
1054
+ return `'${c}', "${t.config.name}"."${t.config.columns[c].name}"`;
1055
+ }).join(", ");
1056
+ return `'${t.alias}', json_build_object(${innerFields})`;
1057
+ }).join(", ")}) AS _nested_data`;
1058
+ } else {
1059
+ const colsKeys = Object.keys(getTableConfig(this._table).columns);
1060
+ if (colsKeys.length === 0) {
1061
+ cols = "*";
1062
+ } else {
1063
+ cols = colsKeys.map((c) => `${qName}."${getTableConfig(this._table).columns[c].name}" AS "${c}"`).join(", ");
1064
+ }
1065
+ }
940
1066
  }
941
1067
  }
942
1068
  let prefix = "";
@@ -950,9 +1076,9 @@ class SelectBuilder {
950
1076
  }
951
1077
  prefix = `WITH ${cteStrs.join(", ")} `;
952
1078
  }
953
- let query = `SELECT ${cols} FROM ${getTableConfig(this._table).qualifiedName}`;
1079
+ let query = `SELECT ${cols} FROM ${qName}`;
954
1080
  if (this._joins.length > 0) {
955
- const joinChunks = this._joins.map((j) => typeof j === "string" ? rawSql(j) : j);
1081
+ const joinChunks = this._joins.map((j) => j.chunk);
956
1082
  const combinedJoins = sqlJoin(joinChunks, " ");
957
1083
  const offset = params.length;
958
1084
  query += " " + combinedJoins.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
@@ -965,11 +1091,11 @@ class SelectBuilder {
965
1091
  params.push(...combined.params);
966
1092
  }
967
1093
  if (this._groupBy.length > 0) {
968
- const qName = getTableConfig(this._table).qualifiedName;
1094
+ const qName2 = getTableConfig(this._table).qualifiedName;
969
1095
  query += " GROUP BY " + this._groupBy.map((c) => {
970
1096
  if (typeof c === "string") {
971
- const dbCol = getTableConfig(this._table).columns[c]?.name ?? c;
972
- return `${qName}."${dbCol}"`;
1097
+ const dbCol = isCte ? c : getTableConfig(this._table).columns[c]?.name ?? c;
1098
+ return `${qName2}."${dbCol}"`;
973
1099
  } else if ("sql" in c && "params" in c) {
974
1100
  const offset = params.length;
975
1101
  params.push(...c.params);
@@ -986,11 +1112,11 @@ class SelectBuilder {
986
1112
  params.push(...combined.params);
987
1113
  }
988
1114
  if (this._orderBy.length > 0) {
989
- const qName = getTableConfig(this._table).qualifiedName;
1115
+ const qName2 = getTableConfig(this._table).qualifiedName;
990
1116
  query += " ORDER BY " + this._orderBy.map((o) => {
991
1117
  if (typeof o.column === "string") {
992
- const dbCol = getTableConfig(this._table).columns[o.column]?.name ?? o.column;
993
- return `${qName}."${dbCol}" ${o.dir.toUpperCase()}`;
1118
+ const dbCol = isCte ? o.column : getTableConfig(this._table).columns[o.column]?.name ?? o.column;
1119
+ return `${qName2}."${dbCol}" ${o.dir.toUpperCase()}`;
994
1120
  } else if ("sql" in o.column && "params" in o.column) {
995
1121
  const offset = params.length;
996
1122
  params.push(...o.column.params);
@@ -1030,8 +1156,8 @@ class SelectBuilderIntermediate {
1030
1156
  this._executor = _executor;
1031
1157
  this._selection = _selection;
1032
1158
  }
1033
- from(table2) {
1034
- return new SelectBuilder(table2, this._executor, this._selection);
1159
+ from(table) {
1160
+ return new SelectBuilder(table, this._executor, this._selection);
1035
1161
  }
1036
1162
  }
1037
1163
 
@@ -1043,8 +1169,8 @@ class UpdateBuilder {
1043
1169
  _returning;
1044
1170
  _comment;
1045
1171
  _with = [];
1046
- constructor(table2, executor) {
1047
- this._table = table2;
1172
+ constructor(table, executor) {
1173
+ this._table = table;
1048
1174
  this._executor = executor;
1049
1175
  }
1050
1176
  then(onfulfilled, onrejected) {
@@ -1107,14 +1233,7 @@ class UpdateBuilder {
1107
1233
  if (colType === "json" || colType === "jsonb") {
1108
1234
  params.push(value);
1109
1235
  } else if (Array.isArray(value)) {
1110
- const pgArray = "{" + value.map((item) => {
1111
- if (item === null || item === undefined)
1112
- return "NULL";
1113
- if (typeof item === "string")
1114
- return '"' + item.replace(/"/g, "\\\"") + '"';
1115
- return typeof item === "object" ? '"' + JSON.stringify(item).replace(/"/g, "\\\"") + '"' : String(item);
1116
- }).join(",") + "}";
1117
- params.push(pgArray);
1236
+ params.push(toPgArray(value));
1118
1237
  } else {
1119
1238
  params.push(JSON.stringify(value));
1120
1239
  }
@@ -1226,8 +1345,8 @@ class RelationalQueryBuilder {
1226
1345
  let cached = schemaCache.get(tableName);
1227
1346
  if (cached)
1228
1347
  return cached;
1229
- const table2 = this._schema[tableName];
1230
- const tConfig = table2[TableConfigSymbol];
1348
+ const table = this._schema[tableName];
1349
+ const tConfig = table[TableConfigSymbol];
1231
1350
  const ones = {};
1232
1351
  const manys = {};
1233
1352
  const manyToManys = {};
@@ -1240,6 +1359,8 @@ class RelationalQueryBuilder {
1240
1359
  }
1241
1360
  for (const [otherName, otherTable] of Object.entries(this._schema)) {
1242
1361
  const otherConfig = otherTable[TableConfigSymbol];
1362
+ if (!otherConfig)
1363
+ continue;
1243
1364
  for (const [colName2, col2] of Object.entries(otherConfig.columns)) {
1244
1365
  if (col2.references && col2.references.table === tableName) {
1245
1366
  const ref = col2.references;
@@ -1250,6 +1371,8 @@ class RelationalQueryBuilder {
1250
1371
  }
1251
1372
  for (const [junctionName, junctionTable] of Object.entries(this._schema)) {
1252
1373
  const junctionConfig = junctionTable[TableConfigSymbol];
1374
+ if (!junctionConfig)
1375
+ continue;
1253
1376
  const refs = Object.entries(junctionConfig.columns).filter(([_, c]) => c.references);
1254
1377
  const toThis = refs.find(([_, c]) => c.references.table === tableName);
1255
1378
  if (toThis) {
@@ -1272,7 +1395,7 @@ class RelationalQueryBuilder {
1272
1395
  schemaCache.set(tableName, result);
1273
1396
  return result;
1274
1397
  }
1275
- _buildSelectJson(tableName, args, alias, params, parentAlias, joinCondition, extraJoin) {
1398
+ _buildSelectJson(tableName, args, alias2, params, parentAlias, joinCondition, extraJoin) {
1276
1399
  const tableConfig = this._schema[tableName][TableConfigSymbol];
1277
1400
  const relations = this._getRuntimeRelations(tableName);
1278
1401
  const withArgs = args.with || {};
@@ -1290,26 +1413,26 @@ class RelationalQueryBuilder {
1290
1413
  continue;
1291
1414
  }
1292
1415
  }
1293
- jsonFields.push(`'${colKey}', "${alias}"."${colConfig.name}"`);
1416
+ jsonFields.push(`'${colKey}', "${alias2}"."${colConfig.name}"`);
1294
1417
  }
1295
1418
  for (const [relKey, relArgs] of Object.entries(withArgs)) {
1296
1419
  const isTrue = relArgs === true;
1297
1420
  const rArgs = isTrue ? {} : relArgs;
1298
1421
  if (relations.ones[relKey]) {
1299
1422
  const rel = relations.ones[relKey];
1300
- const subAlias = `${alias}_${relKey}`;
1423
+ const subAlias = `${alias2}_${relKey}`;
1301
1424
  const targetConfig = this._schema[rel.targetTable][TableConfigSymbol];
1302
1425
  const targetPk = getPkColumn(targetConfig);
1303
- const joinCond = `"${subAlias}"."${targetPk}" = "${alias}"."${rel.sourceColumn}"`;
1304
- const subQuery = this._buildSelectJson(rel.targetTable, rArgs, subAlias, params, alias, joinCond);
1426
+ const joinCond = `"${subAlias}"."${targetPk}" = "${alias2}"."${rel.sourceColumn}"`;
1427
+ const subQuery = this._buildSelectJson(rel.targetTable, rArgs, subAlias, params, alias2, joinCond);
1305
1428
  const subSql = `SELECT ${subQuery.sql} ${subQuery.from}`;
1306
1429
  jsonFields.push(`'${relKey}', (${subSql})`);
1307
1430
  } else if (relations.manys[relKey]) {
1308
1431
  const rel = relations.manys[relKey];
1309
- const subAlias = `${alias}_${relKey}`;
1432
+ const subAlias = `${alias2}_${relKey}`;
1310
1433
  const thisPk = getPkColumn(tableConfig);
1311
- const joinCond = `"${subAlias}"."${rel.targetColumn}" = "${alias}"."${thisPk}"`;
1312
- const subQuery = this._buildSelectJson(rel.targetTable, rArgs, subAlias, params, alias, joinCond);
1434
+ const joinCond = `"${subAlias}"."${rel.targetColumn}" = "${alias2}"."${thisPk}"`;
1435
+ const subQuery = this._buildSelectJson(rel.targetTable, rArgs, subAlias, params, alias2, joinCond);
1313
1436
  const aggAlias = `${subAlias}_agg`;
1314
1437
  const aggSql = `
1315
1438
  SELECT COALESCE(json_agg(_sub.obj), '[]'::json)
@@ -1321,16 +1444,16 @@ class RelationalQueryBuilder {
1321
1444
  jsonFields.push(`'${relKey}', (${aggSql})`);
1322
1445
  } else if (relations.manyToManys[relKey]) {
1323
1446
  const rel = relations.manyToManys[relKey];
1324
- const subAlias = `${alias}_${relKey}`;
1325
- const junctionAlias = `${alias}_j_${relKey}`;
1447
+ const subAlias = `${alias2}_${relKey}`;
1448
+ const junctionAlias = `${alias2}_j_${relKey}`;
1326
1449
  const targetTableConfig = this._schema[rel.targetTable][TableConfigSymbol];
1327
1450
  const junctionTableConfig = this._schema[rel.junctionTable][TableConfigSymbol];
1328
1451
  const fromExtra = `
1329
1452
  INNER JOIN ${junctionTableConfig.qualifiedName} AS "${junctionAlias}"
1330
1453
  ON "${junctionAlias}"."${rel.joinTargetColumn}" = "${subAlias}"."${getPkColumn(targetTableConfig)}"
1331
1454
  `;
1332
- const whereExtra = `"${junctionAlias}"."${rel.joinSourceColumn}" = "${alias}"."${getPkColumn(tableConfig)}"`;
1333
- const subQuery = this._buildSelectJson(rel.targetTable, rArgs, subAlias, params, alias, whereExtra, fromExtra);
1455
+ const whereExtra = `"${junctionAlias}"."${rel.joinSourceColumn}" = "${alias2}"."${getPkColumn(tableConfig)}"`;
1456
+ const subQuery = this._buildSelectJson(rel.targetTable, rArgs, subAlias, params, alias2, whereExtra, fromExtra);
1334
1457
  const aggSql = `
1335
1458
  SELECT COALESCE(json_agg(_sub.obj), '[]'::json)
1336
1459
  FROM (
@@ -1342,7 +1465,7 @@ class RelationalQueryBuilder {
1342
1465
  }
1343
1466
  }
1344
1467
  let selectSql = `json_build_object(${jsonFields.join(", ")})`;
1345
- let fromSql = `FROM ${tableConfig.qualifiedName} AS "${alias}"`;
1468
+ let fromSql = `FROM ${tableConfig.qualifiedName} AS "${alias2}"`;
1346
1469
  if (extraJoin) {
1347
1470
  fromSql += ` ${extraJoin}`;
1348
1471
  }
@@ -1353,7 +1476,7 @@ class RelationalQueryBuilder {
1353
1476
  const offset = params.length;
1354
1477
  let whereChunk = args.where;
1355
1478
  if (args.where && !args.where.sql) {
1356
- whereChunk = parseWhereObject(tableConfig, args.where);
1479
+ whereChunk = parseWhereObject(tableConfig, args.where, alias2);
1357
1480
  }
1358
1481
  if (whereChunk && whereChunk.sql) {
1359
1482
  fromSql += (joinCondition ? " AND " : " WHERE ") + whereChunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
@@ -1368,7 +1491,7 @@ class RelationalQueryBuilder {
1368
1491
  fromSql += ` ORDER BY ` + args.orderBy.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
1369
1492
  params.push(...args.orderBy.params);
1370
1493
  } else {
1371
- const chunks = parseOrderByObject(tableConfig, args.orderBy);
1494
+ const chunks = parseOrderByObject(tableConfig, args.orderBy, alias2);
1372
1495
  if (chunks.length > 0) {
1373
1496
  fromSql += ` ORDER BY ` + chunks.map((c) => {
1374
1497
  const offset = params.length;
@@ -1390,7 +1513,7 @@ class RelationalQueryBuilder {
1390
1513
  }
1391
1514
  buildSQL(args) {
1392
1515
  const params = [];
1393
- const rootAlias = "root";
1516
+ const rootAlias = this._tableName;
1394
1517
  const subQuery = this._buildSelectJson(this._tableName, args, rootAlias, params);
1395
1518
  const sql2 = `SELECT ${subQuery.sql} AS _data ${subQuery.from}`;
1396
1519
  return { sql: sql2, params };
@@ -1466,14 +1589,14 @@ class BungresDB {
1466
1589
  }
1467
1590
  return new SelectBuilderIntermediate(this);
1468
1591
  }
1469
- insert(table2) {
1470
- return new InsertBuilder(table2, this);
1592
+ insert(table) {
1593
+ return new InsertBuilder(table, this);
1471
1594
  }
1472
- update(table2) {
1473
- return new UpdateBuilder(table2, this);
1595
+ update(table) {
1596
+ return new UpdateBuilder(table, this);
1474
1597
  }
1475
- delete(table2) {
1476
- return new DeleteBuilder(table2, this);
1598
+ delete(table) {
1599
+ return new DeleteBuilder(table, this);
1477
1600
  }
1478
1601
  async execute(builder) {
1479
1602
  await this.ready();
@@ -1517,14 +1640,14 @@ class BungresTransaction {
1517
1640
  }
1518
1641
  return new SelectBuilderIntermediate(this);
1519
1642
  }
1520
- insert(table2) {
1521
- return new InsertBuilder(table2, this);
1643
+ insert(table) {
1644
+ return new InsertBuilder(table, this);
1522
1645
  }
1523
- update(table2) {
1524
- return new UpdateBuilder(table2, this);
1646
+ update(table) {
1647
+ return new UpdateBuilder(table, this);
1525
1648
  }
1526
- delete(table2) {
1527
- return new DeleteBuilder(table2, this);
1649
+ delete(table) {
1650
+ return new DeleteBuilder(table, this);
1528
1651
  }
1529
1652
  async execute(builder) {
1530
1653
  const chunk = "toSQL" in builder ? builder.toSQL() : builder;
@@ -1683,15 +1806,30 @@ function formatDefaultValue(value, dataType) {
1683
1806
  return value ? "TRUE" : "FALSE";
1684
1807
  if (typeof value === "number")
1685
1808
  return String(value);
1809
+ if (value instanceof Date)
1810
+ return `'${value.toISOString()}'`;
1811
+ if (Array.isArray(value)) {
1812
+ if (dataType === "json" || dataType === "jsonb") {
1813
+ return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
1814
+ }
1815
+ const pgArray = "{" + value.map((item) => {
1816
+ if (item === null || item === undefined)
1817
+ return "NULL";
1818
+ if (typeof item === "string")
1819
+ return '"' + item.replace(/"/g, "\\\"") + '"';
1820
+ return typeof item === "object" ? '"' + JSON.stringify(item).replace(/"/g, "\\\"").replace(/'/g, "''") + '"' : String(item);
1821
+ }).join(",") + "}";
1822
+ return `'${pgArray.replace(/'/g, "''")}'`;
1823
+ }
1686
1824
  return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
1687
1825
  }
1688
- function buildIndex(table2, idx) {
1689
- const tableName = table2.schema ? `"${table2.schema}"."${table2.name}"` : `"${table2.name}"`;
1826
+ function buildIndex(table, idx) {
1827
+ const tableName = table.schema ? `"${table.schema}"."${table.name}"` : `"${table.name}"`;
1690
1828
  const unique2 = idx.unique ? "UNIQUE " : "";
1691
1829
  const using = idx.using ? ` USING ${idx.using.toUpperCase()}` : "";
1692
1830
  const cols = idx.columns.map((c) => `"${c}"`).join(", ");
1693
1831
  const where = idx.where ? ` WHERE ${idx.where}` : "";
1694
- const idxName = idx.name ?? `idx_${table2.name}_${idx.columns.join("_")}`;
1832
+ const idxName = idx.name ?? `idx_${table.name}_${idx.columns.join("_")}`;
1695
1833
  return `CREATE ${unique2}INDEX IF NOT EXISTS "${idxName}" ON ${tableName}${using} (${cols})${where};`;
1696
1834
  }
1697
1835
  function generateAddColumn(tableName, schema, key, col2) {
@@ -1726,7 +1864,14 @@ function inlineParams(chunk) {
1726
1864
  }
1727
1865
  function generateCreateView(view) {
1728
1866
  const kind = view.materialized ? "MATERIALIZED VIEW" : "VIEW";
1729
- const inlineSql = inlineParams(view.query.toSQL());
1867
+ let inlineSql = "";
1868
+ if (view.sql) {
1869
+ inlineSql = view.sql;
1870
+ } else if (view.query && typeof view.query.toSQL === "function") {
1871
+ inlineSql = inlineParams(view.query.toSQL());
1872
+ } else {
1873
+ inlineSql = "SELECT * FROM (VALUES ('LEGACY_SNAPSHOT_MISSING_SQL')) AS t(c)";
1874
+ }
1730
1875
  return `CREATE ${kind} "${view.name}" AS ${inlineSql};`;
1731
1876
  }
1732
1877
  function generateDropView(view, ifExists = true) {
@@ -1961,8 +2106,8 @@ function topoSortConfigs(tables) {
1961
2106
  visit(dep);
1962
2107
  result.push(config);
1963
2108
  }
1964
- for (const table2 of tables)
1965
- visit(table2.name);
2109
+ for (const table of tables)
2110
+ visit(table.name);
1966
2111
  return result;
1967
2112
  }
1968
2113
  function diffColumn(prev, next) {
@@ -3120,6 +3265,30 @@ ${i}` : `${s}${styleText("inverse", r2)}${i}`;
3120
3265
  });
3121
3266
  }
3122
3267
  }
3268
+ class n extends V {
3269
+ get userInputWithCursor() {
3270
+ if (this.state === "submit")
3271
+ return this.userInput;
3272
+ const t2 = this.userInput;
3273
+ if (this.cursor >= t2.length)
3274
+ return `${this.userInput}\u2588`;
3275
+ const r2 = t2.slice(0, this.cursor), s = t2.slice(this.cursor, this.cursor + 1), e = t2.slice(this.cursor + 1);
3276
+ return `${r2}${styleText("inverse", s)}${e}`;
3277
+ }
3278
+ get cursor() {
3279
+ return this._cursor;
3280
+ }
3281
+ constructor(t2) {
3282
+ super({
3283
+ ...t2,
3284
+ initialUserInput: t2.initialUserInput ?? t2.initialValue
3285
+ }), this.on("userInput", (r2) => {
3286
+ this._setValue(r2);
3287
+ }), this.on("finalize", () => {
3288
+ this.value || (this.value = t2.defaultValue), this.value === undefined && (this.value = "");
3289
+ });
3290
+ }
3291
+ }
3123
3292
 
3124
3293
  // ../../node_modules/.bun/@clack+prompts@1.7.0/node_modules/@clack/prompts/dist/index.mjs
3125
3294
  import { styleText as styleText2, stripVTControlCharacters } from "util";
@@ -3380,24 +3549,62 @@ var SELECT_INSTRUCTIONS = [
3380
3549
  `${styleText2("dim", "Enter:")} confirm`
3381
3550
  ];
3382
3551
  var i = `${styleText2("gray", S_BAR)} `;
3552
+ var text = (e) => new n({
3553
+ validate: e.validate,
3554
+ placeholder: e.placeholder,
3555
+ defaultValue: e.defaultValue,
3556
+ initialValue: e.initialValue,
3557
+ output: e.output,
3558
+ signal: e.signal,
3559
+ input: e.input,
3560
+ render() {
3561
+ const i2 = e?.withGuide ?? settings.withGuide, s = `${`${i2 ? `${styleText2("gray", S_BAR)}
3562
+ ` : ""}${symbol(this.state)} `}${e.message}
3563
+ `, c2 = e.placeholder && e.placeholder.length > 0 ? styleText2("inverse", e.placeholder[0]) + styleText2("dim", e.placeholder.slice(1)) : styleText2(["inverse", "hidden"], "_"), o2 = this.userInput ? this.userInputWithCursor : c2, l2 = this.value ?? "";
3564
+ switch (this.state) {
3565
+ case "error": {
3566
+ const n2 = this.error ? ` ${styleText2("yellow", this.error)}` : "", r2 = i2 ? `${styleText2("yellow", S_BAR)} ` : "", d = i2 ? styleText2("yellow", S_BAR_END) : "";
3567
+ return `${s.trim()}
3568
+ ${r2}${o2}
3569
+ ${d}${n2}
3570
+ `;
3571
+ }
3572
+ case "submit": {
3573
+ const n2 = l2 ? ` ${styleText2("dim", l2)}` : "", r2 = i2 ? styleText2("gray", S_BAR) : "";
3574
+ return `${s}${r2}${n2}`;
3575
+ }
3576
+ case "cancel": {
3577
+ const n2 = l2 ? ` ${styleText2(["strikethrough", "dim"], l2)}` : "", r2 = i2 ? styleText2("gray", S_BAR) : "";
3578
+ return `${s}${r2}${n2}${l2.trim() ? `
3579
+ ${r2}` : ""}`;
3580
+ }
3581
+ default: {
3582
+ const n2 = i2 ? `${styleText2("cyan", S_BAR)} ` : "", r2 = i2 ? styleText2("cyan", S_BAR_END) : "";
3583
+ return `${s}${n2}${o2}
3584
+ ${r2}
3585
+ `;
3586
+ }
3587
+ }
3588
+ }
3589
+ }).prompt();
3383
3590
 
3384
3591
  // src/commands/push.ts
3385
3592
  var import_picocolors = __toESM(require_picocolors(), 1);
3386
3593
  async function runPush(config, opts = {}) {
3387
3594
  intro(import_picocolors.default.bgCyan(import_picocolors.default.black(" @bungres/kit push ")));
3388
- const s = spinner();
3389
- s.start("Loading schemas...");
3595
+ let activeSpinner = spinner();
3596
+ activeSpinner.start("Loading schemas...");
3390
3597
  const schemas = await loadSchemas(config.schema);
3391
3598
  if (schemas.length === 0) {
3392
- s.stop("No schemas found.");
3599
+ activeSpinner.stop("No schemas found.");
3393
3600
  log.warn(import_picocolors.default.yellow("No table definitions found. Check your schema glob pattern."));
3394
3601
  outro("Failed.");
3395
3602
  return;
3396
3603
  }
3397
- s.stop(`Loaded ${schemas.length} schemas.`);
3604
+ activeSpinner.stop(`Loaded ${schemas.length} schemas.`);
3398
3605
  const sql2 = new Bun.SQL(config.dbUrl);
3399
- const ms = spinner();
3400
- ms.start("Computing diff from database...");
3606
+ activeSpinner = spinner();
3607
+ activeSpinner.start("Computing diff from database...");
3401
3608
  try {
3402
3609
  await sql2.unsafe(`CREATE SCHEMA IF NOT EXISTS "${config.migrationsSchema}";`);
3403
3610
  const qualifiedPush = `"${config.migrationsSchema}"."__bungres_push"`;
@@ -3422,23 +3629,27 @@ async function runPush(config, opts = {}) {
3422
3629
  }
3423
3630
  }
3424
3631
  const currentSnapshot = { tables: {}, enums: {}, views: {} };
3425
- for (const s2 of schemas) {
3426
- if (s2.type === "table") {
3427
- currentSnapshot.tables[s2.config.name] = s2.config;
3428
- } else if (s2.type === "enum") {
3429
- currentSnapshot.enums[s2.enumName] = { enumName: s2.enumName, enumValues: s2.enumValues };
3430
- } else if (s2.type === "view") {
3431
- currentSnapshot.views[s2.config.name] = s2.config;
3632
+ for (const s of schemas) {
3633
+ if (s.type === "table") {
3634
+ currentSnapshot.tables[s.config.name] = s.config;
3635
+ } else if (s.type === "enum") {
3636
+ currentSnapshot.enums[s.enumName] = { enumName: s.enumName, enumValues: s.enumValues };
3637
+ } else if (s.type === "view") {
3638
+ currentSnapshot.views[s.config.name] = {
3639
+ name: s.config.name,
3640
+ materialized: s.config.materialized,
3641
+ sql: typeof s.config.query?.toSQL === "function" ? inlineParams(s.config.query.toSQL()) : s.config.sql
3642
+ };
3432
3643
  }
3433
3644
  }
3434
3645
  const diff = diffSchemas(prevSnapshot, currentSnapshot);
3435
3646
  if (diff.statements.length === 0) {
3436
- ms.stop("Up to date.");
3647
+ activeSpinner.stop("Up to date.");
3437
3648
  log.success(import_picocolors.default.green("No schema changes detected. Database is up to date."));
3438
3649
  outro("Done.");
3439
3650
  return;
3440
3651
  }
3441
- ms.stop(`Computed ${diff.statements.length} statements.`);
3652
+ activeSpinner.stop(`Computed ${diff.statements.length} statements.`);
3442
3653
  log.message(import_picocolors.default.bold("Changes to apply:"));
3443
3654
  for (const change of diff.summary) {
3444
3655
  if (change.startsWith("CREATE") || change.startsWith("ALTER TABLE") && change.includes("ADD")) {
@@ -3465,8 +3676,8 @@ async function runPush(config, opts = {}) {
3465
3676
  return;
3466
3677
  }
3467
3678
  }
3468
- const exSpinner = spinner();
3469
- exSpinner.start("Pushing changes...");
3679
+ activeSpinner = spinner();
3680
+ activeSpinner.start("Pushing changes...");
3470
3681
  for (const stmt of diff.statements) {
3471
3682
  if (config.verbose) {
3472
3683
  log.info(import_picocolors.default.gray(`-- ${stmt}`));
@@ -3474,9 +3685,10 @@ async function runPush(config, opts = {}) {
3474
3685
  await sql2.unsafe(stmt);
3475
3686
  }
3476
3687
  await sql2.unsafe(`INSERT INTO ${qualifiedPush} (snapshot) VALUES ($1);`, [JSON.stringify(currentSnapshot)]);
3477
- exSpinner.stop(import_picocolors.default.green("Changes applied successfully."));
3688
+ activeSpinner.stop(import_picocolors.default.green("Changes applied successfully."));
3478
3689
  outro(import_picocolors.default.cyan("\u2728 Push complete."));
3479
3690
  } catch (err) {
3691
+ activeSpinner.stop("Failed.");
3480
3692
  log.error(import_picocolors.default.red(`Push failed: ${err.message}`));
3481
3693
  outro("Failed.");
3482
3694
  } finally {
@@ -3485,7 +3697,7 @@ async function runPush(config, opts = {}) {
3485
3697
  }
3486
3698
  // src/commands/generate.ts
3487
3699
  import { join as join3, resolve as resolve3 } from "path";
3488
- import { readdirSync } from "fs";
3700
+ import { readdirSync, existsSync, statSync } from "fs";
3489
3701
  var import_picocolors2 = __toESM(require_picocolors(), 1);
3490
3702
  async function runGenerate(config, name) {
3491
3703
  intro(import_picocolors2.default.bgCyan(import_picocolors2.default.black(" @bungres/kit generate ")));
@@ -3500,6 +3712,12 @@ async function runGenerate(config, name) {
3500
3712
  }
3501
3713
  s.stop(`Loaded ${schemas.length} schemas.`);
3502
3714
  const migrationsDir = resolve3(config.out);
3715
+ if (existsSync(migrationsDir) && !statSync(migrationsDir).isDirectory()) {
3716
+ s.stop("Failed.");
3717
+ log.error(import_picocolors2.default.red(`Migration path exists but is not a directory: ${migrationsDir}`));
3718
+ outro("Failed.");
3719
+ return;
3720
+ }
3503
3721
  const metaDir = join3(migrationsDir, "meta");
3504
3722
  await Bun.$`mkdir -p ${migrationsDir}`.quiet();
3505
3723
  await Bun.$`mkdir -p ${metaDir}`.quiet();
@@ -3510,7 +3728,11 @@ async function runGenerate(config, name) {
3510
3728
  } else if (s2.type === "enum") {
3511
3729
  currentSnapshot.enums[s2.enumName] = { enumName: s2.enumName, enumValues: s2.enumValues };
3512
3730
  } else if (s2.type === "view") {
3513
- currentSnapshot.views[s2.config.name] = s2.config;
3731
+ currentSnapshot.views[s2.config.name] = {
3732
+ name: s2.config.name,
3733
+ materialized: s2.config.materialized,
3734
+ sql: typeof s2.config.query?.toSQL === "function" ? inlineParams(s2.config.query.toSQL()) : s2.config.sql
3735
+ };
3514
3736
  }
3515
3737
  }
3516
3738
  let prevSnapshot = { tables: {}, enums: {}, views: {} };
@@ -3670,14 +3892,30 @@ function topoSort(schemas) {
3670
3892
  }
3671
3893
  // src/commands/migrate.ts
3672
3894
  import { join as join4, resolve as resolve4 } from "path";
3895
+ import { existsSync as existsSync2, statSync as statSync2 } from "fs";
3673
3896
  var import_picocolors3 = __toESM(require_picocolors(), 1);
3674
3897
  async function runMigrate(config) {
3675
3898
  intro(import_picocolors3.default.bgCyan(import_picocolors3.default.black(" @bungres/kit migrate ")));
3676
3899
  const migrationsDir = resolve4(config.out);
3900
+ let s = spinner();
3901
+ s.start("Checking pending migrations...");
3902
+ if (!existsSync2(migrationsDir)) {
3903
+ s.stop("No files found.");
3904
+ log.warn(import_picocolors3.default.yellow(`Migration directory does not exist: ${migrationsDir}`));
3905
+ log.info(`Run ${import_picocolors3.default.green("bungres generate")} first.`);
3906
+ outro("Done.");
3907
+ return;
3908
+ }
3909
+ if (!statSync2(migrationsDir).isDirectory()) {
3910
+ s.stop("Failed.");
3911
+ log.error(import_picocolors3.default.red(`Migration path exists but is not a directory: ${migrationsDir}`));
3912
+ outro("Failed.");
3913
+ return;
3914
+ }
3677
3915
  const sql2 = new Bun.SQL(config.dbUrl);
3678
- const table2 = config.migrationsTable;
3916
+ const table = config.migrationsTable;
3679
3917
  const schema = config.migrationsSchema;
3680
- const qualifiedTable = `"${schema}"."${table2}"`;
3918
+ const qualifiedTable = `"${schema}"."${table}"`;
3681
3919
  const createSchema = `CREATE SCHEMA IF NOT EXISTS "${schema}";`;
3682
3920
  const createMigrationsTable = `
3683
3921
  CREATE TABLE IF NOT EXISTS ${qualifiedTable} (
@@ -3685,8 +3923,6 @@ CREATE TABLE IF NOT EXISTS ${qualifiedTable} (
3685
3923
  name TEXT NOT NULL UNIQUE,
3686
3924
  applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
3687
3925
  );`.trim();
3688
- const s = spinner();
3689
- s.start("Checking pending migrations...");
3690
3926
  try {
3691
3927
  await sql2.unsafe(createSchema);
3692
3928
  await sql2.unsafe(createMigrationsTable);
@@ -3714,9 +3950,24 @@ CREATE TABLE IF NOT EXISTS ${qualifiedTable} (
3714
3950
  }
3715
3951
  s.stop(`Found ${pending.length} pending migration(s).`);
3716
3952
  for (const file of pending) {
3717
- const ms = spinner();
3718
- ms.start(`Applying ${file}...`);
3719
- const content = await Bun.file(join4(migrationsDir, file)).text();
3953
+ s = spinner();
3954
+ s.start(`Applying ${file}...`);
3955
+ const filePath = join4(migrationsDir, file);
3956
+ if (!existsSync2(filePath)) {
3957
+ s.stop("Failed.");
3958
+ log.error(import_picocolors3.default.red(`Migration file not found: ${filePath}`));
3959
+ outro("Failed.");
3960
+ return;
3961
+ }
3962
+ let content = "";
3963
+ try {
3964
+ content = await Bun.file(filePath).text();
3965
+ } catch (err) {
3966
+ s.stop("Failed.");
3967
+ log.error(import_picocolors3.default.red(`Failed to read migration file ${file}: ${err.message}`));
3968
+ outro("Failed.");
3969
+ return;
3970
+ }
3720
3971
  let upContent = content;
3721
3972
  const upMatch = content.match(/-- ==== UP ====([\s\S]*?)(?:-- ==== DOWN ====|$)/i);
3722
3973
  if (upMatch) {
@@ -3734,10 +3985,11 @@ ${upContent}
3734
3985
  }
3735
3986
  await txSql.unsafe(`INSERT INTO ${qualifiedTable} (name) VALUES ($1)`, [file]);
3736
3987
  });
3737
- ms.stop(import_picocolors3.default.green(`\u2713 Applied ${file}`));
3988
+ s.stop(import_picocolors3.default.green(`\u2713 Applied ${file}`));
3738
3989
  }
3739
3990
  outro(import_picocolors3.default.cyan("\u2728 All migrations applied successfully."));
3740
3991
  } catch (err) {
3992
+ s.stop("Failed.");
3741
3993
  log.error(import_picocolors3.default.red(`Migration failed: ${err.message}`));
3742
3994
  outro("Failed.");
3743
3995
  } finally {
@@ -3851,10 +4103,10 @@ function generateSchemaTS(tableMap, dbSchema) {
3851
4103
  `} from "@bungres/orm";`,
3852
4104
  ``
3853
4105
  ];
3854
- for (const [, table2] of tableMap) {
3855
- const varName = toCamelCase(table2.tableName);
3856
- lines.push(`export const ${varName} = pgTable("${table2.tableName}", {`);
3857
- for (const col2 of table2.columns) {
4106
+ for (const [, table] of tableMap) {
4107
+ const varName = toCamelCase(table.tableName);
4108
+ lines.push(`export const ${varName} = pgTable("${table.tableName}", {`);
4109
+ for (const col2 of table.columns) {
3858
4110
  const colExpr = buildColumnExpression(col2);
3859
4111
  lines.push(` ${toCamelCase(col2.name)}: ${colExpr},`);
3860
4112
  }
@@ -3862,9 +4114,9 @@ function generateSchemaTS(tableMap, dbSchema) {
3862
4114
  if (dbSchema !== "public") {
3863
4115
  options.push(`schema: "${dbSchema}"`);
3864
4116
  }
3865
- if (table2.indexes.length > 0) {
4117
+ if (table.indexes.length > 0) {
3866
4118
  const idxLines = [];
3867
- for (const idx of table2.indexes) {
4119
+ for (const idx of table.indexes) {
3868
4120
  const m2 = idx.indexdef.match(/CREATE (UNIQUE )?INDEX (.+) ON (.+) USING (\w+) \((.+)\)(?: WHERE (.+))?/i);
3869
4121
  if (m2) {
3870
4122
  const isUnique = !!m2[1];
@@ -4001,22 +4253,36 @@ function toCamelCase(str) {
4001
4253
  return str.replace(/_([a-z])/g, (_2, c2) => c2.toUpperCase());
4002
4254
  }
4003
4255
  // src/commands/status.ts
4004
- import { resolve as resolve6 } from "path";
4005
4256
  var import_picocolors4 = __toESM(require_picocolors(), 1);
4257
+ import { existsSync as existsSync3, statSync as statSync3 } from "fs";
4258
+ import { resolve as resolve6 } from "path";
4006
4259
  async function runStatus(config) {
4007
4260
  intro(import_picocolors4.default.bgCyan(import_picocolors4.default.black(" @bungres/kit status ")));
4008
4261
  const migrationsDir = resolve6(config.out);
4009
- const sql2 = new Bun.SQL(config.dbUrl);
4010
- const table2 = config.migrationsTable;
4011
- const schema = config.migrationsSchema;
4012
- const qualifiedTable = `"${schema}"."${table2}"`;
4013
4262
  const s = spinner();
4014
4263
  s.start("Checking migration status...");
4264
+ if (!existsSync3(migrationsDir)) {
4265
+ s.stop("No migration directory found.");
4266
+ log.warn(import_picocolors4.default.yellow(`Migration directory does not exist: ${migrationsDir}`));
4267
+ log.info(`Run ${import_picocolors4.default.green("bungres generate")} first.`);
4268
+ outro("Done.");
4269
+ return;
4270
+ }
4271
+ if (!statSync3(migrationsDir).isDirectory()) {
4272
+ s.stop("Failed.");
4273
+ log.error(import_picocolors4.default.red(`Migration path exists but is not a directory: ${migrationsDir}`));
4274
+ outro("Failed.");
4275
+ return;
4276
+ }
4277
+ const sql2 = new Bun.SQL(config.dbUrl);
4278
+ const table = config.migrationsTable;
4279
+ const schema = config.migrationsSchema;
4280
+ const qualifiedTable = `"${schema}"."${table}"`;
4015
4281
  try {
4016
4282
  const tableCheck = await sql2.unsafe(`SELECT EXISTS (
4017
4283
  SELECT 1 FROM information_schema.tables
4018
4284
  WHERE table_schema = $1 AND table_name = $2
4019
- ) AS exists`, [schema, table2]);
4285
+ ) AS exists`, [schema, table]);
4020
4286
  const trackingExists = tableCheck[0]?.exists ?? false;
4021
4287
  const glob = new Bun.Glob("*.sql");
4022
4288
  const files = [];
@@ -4051,6 +4317,7 @@ async function runStatus(config) {
4051
4317
  log.info(`${import_picocolors4.default.green(appliedSet.size.toString())} applied, ${import_picocolors4.default.yellow(pendingCount.toString())} pending.`);
4052
4318
  outro("Done.");
4053
4319
  } catch (err) {
4320
+ s.stop("Failed.");
4054
4321
  log.error(import_picocolors4.default.red(`Status check failed: ${err.message}`));
4055
4322
  outro("Failed.");
4056
4323
  } finally {
@@ -4061,26 +4328,14 @@ async function runStatus(config) {
4061
4328
  var import_picocolors5 = __toESM(require_picocolors(), 1);
4062
4329
  async function runDrop(config, opts = {}) {
4063
4330
  intro(import_picocolors5.default.bgCyan(import_picocolors5.default.black(" @bungres/kit drop ")));
4064
- const s = spinner();
4065
- s.start("Loading schemas...");
4066
- const schemas = await loadSchemas(config.schema);
4067
- const tableSchemas = schemas.filter((s2) => s2.type === "table");
4068
- const enumSchemas = schemas.filter((s2) => s2.type === "enum");
4069
- const viewSchemas = schemas.filter((s2) => s2.type === "view");
4070
- if (tableSchemas.length === 0 && enumSchemas.length === 0 && viewSchemas.length === 0) {
4071
- s.stop("No schemas found.");
4072
- log.warn(import_picocolors5.default.yellow("No definitions found in schema files."));
4073
- outro("Failed.");
4074
- return;
4075
- }
4076
- s.stop(`Loaded ${schemas.length} schemas.`);
4077
4331
  const ms = spinner();
4078
- ms.start("Checking database tables...");
4332
+ ms.start("Scanning database for objects to drop...");
4079
4333
  const sql2 = new Bun.SQL(config.dbUrl);
4080
4334
  try {
4081
- const userSchema = config.dbSchema;
4082
- const existingUserTablesResult = await sql2.unsafe(`SELECT table_name FROM information_schema.tables WHERE table_schema = $1`, [userSchema]);
4083
- const existingTableNames = new Set(existingUserTablesResult.map((r2) => r2.table_name));
4335
+ const userSchema = config.dbSchema || "public";
4336
+ const existingViews = await sql2.unsafe(`SELECT table_name FROM information_schema.views WHERE table_schema = $1`, [userSchema]);
4337
+ const existingTables = await sql2.unsafe(`SELECT table_name FROM information_schema.tables WHERE table_schema = $1 AND table_type = 'BASE TABLE'`, [userSchema]);
4338
+ const existingTypes = await sql2.unsafe(`SELECT t.typname FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = $1 AND t.typtype = 'e'`, [userSchema]);
4084
4339
  const migTableCheck = await sql2.unsafe(`SELECT EXISTS (
4085
4340
  SELECT 1 FROM information_schema.tables
4086
4341
  WHERE table_schema = $1 AND table_name = $2
@@ -4091,35 +4346,20 @@ async function runDrop(config, opts = {}) {
4091
4346
  WHERE table_schema = $1 AND table_name = $2
4092
4347
  ) AS exists`, [config.migrationsSchema, "__bungres_push"]);
4093
4348
  const pushTableExists = pushTableCheck[0]?.exists ?? false;
4094
- const tablesToDrop = tableSchemas.filter((sch) => existingTableNames.has(sch.config.name));
4095
- if (tablesToDrop.length === 0 && enumSchemas.length === 0 && viewSchemas.length === 0 && !migrationTableExists && !pushTableExists) {
4349
+ const totalObjects = existingViews.length + existingTables.length + existingTypes.length + (migrationTableExists ? 1 : 0) + (pushTableExists ? 1 : 0);
4350
+ if (totalObjects === 0) {
4096
4351
  ms.stop("Nothing to do.");
4097
- log.success(import_picocolors5.default.green("No items to drop (they either don't exist or were already dropped)."));
4352
+ log.success(import_picocolors5.default.green("No items to drop (schema is empty)."));
4098
4353
  outro("Done.");
4099
4354
  return;
4100
4355
  }
4101
- ms.stop("Database check complete.");
4102
- const namesToPrint = tablesToDrop.map((sch) => (sch.config.schema ? sch.config.schema + "." : "") + sch.config.name + " (table)");
4103
- for (const v of viewSchemas) {
4104
- namesToPrint.push(`${v.config.name} (view)`);
4105
- }
4106
- for (const e of enumSchemas) {
4107
- namesToPrint.push(`${e.enumName} (enum)`);
4108
- }
4109
- if (migrationTableExists) {
4110
- namesToPrint.push(`${config.migrationsSchema}.${config.migrationsTable} (table)`);
4111
- }
4112
- if (pushTableExists) {
4113
- namesToPrint.push(`${config.migrationsSchema}.__bungres_push (table)`);
4114
- }
4356
+ ms.stop("Database scan complete.");
4115
4357
  if (!opts.force) {
4116
4358
  log.warn(import_picocolors5.default.bgRed(import_picocolors5.default.white(" \u26A0\uFE0F WARNING ")));
4117
- log.message(import_picocolors5.default.bold(import_picocolors5.default.red("This will drop the following items and ALL their data:")));
4118
- for (const name of namesToPrint) {
4119
- log.info(import_picocolors5.default.yellow(` - ${name}`));
4120
- }
4359
+ log.message(import_picocolors5.default.bold(import_picocolors5.default.red("This will GLOBALLY drop all tables, views, and types in the database schema!")));
4360
+ log.info(import_picocolors5.default.yellow(`Found ${existingViews.length} views, ${existingTables.length} tables, ${existingTypes.length} enums.`));
4121
4361
  const confirm2 = await confirm({
4122
- message: "Are you sure you want to proceed?",
4362
+ message: "Are you absolutely sure you want to drop ALL data?",
4123
4363
  initialValue: false
4124
4364
  });
4125
4365
  if (isCancel(confirm2) || !confirm2) {
@@ -4128,35 +4368,36 @@ async function runDrop(config, opts = {}) {
4128
4368
  }
4129
4369
  }
4130
4370
  const exSpinner = spinner();
4131
- exSpinner.start("Dropping tables...");
4132
- for (const entry of viewSchemas) {
4133
- const isMat = entry.config.materialized ? "MATERIALIZED VIEW" : "VIEW";
4134
- const ddl = `DROP ${isMat} IF EXISTS "${entry.config.name}" CASCADE;`;
4135
- await sql2.unsafe(ddl);
4136
- log.step(import_picocolors5.default.gray(`Dropped ${entry.config.name}`));
4137
- }
4138
- for (const entry of tablesToDrop) {
4139
- const ddl = `DROP TABLE IF EXISTS "${entry.config.name}" CASCADE;`;
4140
- await sql2.unsafe(ddl);
4141
- log.step(import_picocolors5.default.gray(`Dropped ${entry.config.name}`));
4142
- }
4143
- for (const entry of enumSchemas) {
4144
- const ddl = `DROP TYPE IF EXISTS "${entry.enumName}" CASCADE;`;
4145
- await sql2.unsafe(ddl);
4146
- log.step(import_picocolors5.default.gray(`Dropped ${entry.enumName}`));
4371
+ exSpinner.start("Dropping objects...");
4372
+ for (const v of existingViews) {
4373
+ await sql2.unsafe(`DROP VIEW IF EXISTS "${userSchema}"."${v.table_name}" CASCADE;`);
4374
+ if (opts.force !== true)
4375
+ log.step(import_picocolors5.default.gray(`Dropped view ${v.table_name}`));
4376
+ }
4377
+ for (const t2 of existingTables) {
4378
+ await sql2.unsafe(`DROP TABLE IF EXISTS "${userSchema}"."${t2.table_name}" CASCADE;`);
4379
+ if (opts.force !== true)
4380
+ log.step(import_picocolors5.default.gray(`Dropped table ${t2.table_name}`));
4381
+ }
4382
+ for (const type of existingTypes) {
4383
+ await sql2.unsafe(`DROP TYPE IF EXISTS "${userSchema}"."${type.typname}" CASCADE;`);
4384
+ if (opts.force !== true)
4385
+ log.step(import_picocolors5.default.gray(`Dropped enum ${type.typname}`));
4147
4386
  }
4148
4387
  if (migrationTableExists) {
4149
- const qualifiedMigTable = `"${config.migrationsSchema}"."${config.migrationsTable}"`;
4150
- await sql2.unsafe(`DROP TABLE IF EXISTS ${qualifiedMigTable} CASCADE`);
4151
- log.step(import_picocolors5.default.gray(`Dropped ${config.migrationsSchema}.${config.migrationsTable}`));
4388
+ await sql2.unsafe(`DROP TABLE IF EXISTS "${config.migrationsSchema}"."${config.migrationsTable}" CASCADE`);
4389
+ if (opts.force !== true)
4390
+ log.step(import_picocolors5.default.gray(`Dropped ${config.migrationsSchema}.${config.migrationsTable}`));
4152
4391
  }
4153
4392
  if (pushTableExists) {
4154
4393
  await sql2.unsafe(`DROP TABLE IF EXISTS "${config.migrationsSchema}"."__bungres_push" CASCADE`);
4155
- log.step(import_picocolors5.default.gray(`Dropped ${config.migrationsSchema}.__bungres_push`));
4394
+ if (opts.force !== true)
4395
+ log.step(import_picocolors5.default.gray(`Dropped ${config.migrationsSchema}.__bungres_push`));
4156
4396
  }
4157
4397
  exSpinner.stop("Items dropped.");
4158
4398
  outro(import_picocolors5.default.cyan("\u2728 Drop complete."));
4159
4399
  } catch (err) {
4400
+ ms.stop("Scan failed.");
4160
4401
  log.error(import_picocolors5.default.red(`Drop failed: ${err.message}`));
4161
4402
  outro("Failed.");
4162
4403
  } finally {