@bungres/kit 0.7.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -55,12 +55,12 @@ var require_src = __commonJS((exports, module) => {
55
55
  ret += `${CSI2}${y}B`;
56
56
  return ret;
57
57
  },
58
- up: (count2 = 1) => `${CSI2}${count2}A`,
59
- down: (count2 = 1) => `${CSI2}${count2}B`,
60
- forward: (count2 = 1) => `${CSI2}${count2}C`,
61
- backward: (count2 = 1) => `${CSI2}${count2}D`,
62
- nextLine: (count2 = 1) => `${CSI2}E`.repeat(count2),
63
- prevLine: (count2 = 1) => `${CSI2}F`.repeat(count2),
58
+ up: (count = 1) => `${CSI2}${count}A`,
59
+ down: (count = 1) => `${CSI2}${count}B`,
60
+ forward: (count = 1) => `${CSI2}${count}C`,
61
+ backward: (count = 1) => `${CSI2}${count}D`,
62
+ nextLine: (count = 1) => `${CSI2}E`.repeat(count),
63
+ prevLine: (count = 1) => `${CSI2}F`.repeat(count),
64
64
  left: `${CSI2}G`,
65
65
  hide: `${CSI2}?25l`,
66
66
  show: `${CSI2}?25h`,
@@ -68,21 +68,21 @@ var require_src = __commonJS((exports, module) => {
68
68
  restore: `${ESC2}8`
69
69
  };
70
70
  var scroll = {
71
- up: (count2 = 1) => `${CSI2}S`.repeat(count2),
72
- down: (count2 = 1) => `${CSI2}T`.repeat(count2)
71
+ up: (count = 1) => `${CSI2}S`.repeat(count),
72
+ down: (count = 1) => `${CSI2}T`.repeat(count)
73
73
  };
74
74
  var erase = {
75
75
  screen: `${CSI2}2J`,
76
- up: (count2 = 1) => `${CSI2}1J`.repeat(count2),
77
- down: (count2 = 1) => `${CSI2}J`.repeat(count2),
76
+ up: (count = 1) => `${CSI2}1J`.repeat(count),
77
+ down: (count = 1) => `${CSI2}J`.repeat(count),
78
78
  line: `${CSI2}2K`,
79
79
  lineEnd: `${CSI2}K`,
80
80
  lineStart: `${CSI2}1K`,
81
- lines(count2) {
81
+ lines(count) {
82
82
  let clear = "";
83
- for (let i = 0;i < count2; i++)
84
- clear += this.line + (i < count2 - 1 ? cursor.up() : "");
85
- if (count2)
83
+ for (let i = 0;i < count; i++)
84
+ clear += this.line + (i < count - 1 ? cursor.up() : "");
85
+ if (count)
86
86
  clear += cursor.left;
87
87
  return clear;
88
88
  }
@@ -97,16 +97,16 @@ var require_picocolors = __commonJS((exports, module) => {
97
97
  var env = p2.env || {};
98
98
  var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p2.platform === "win32" || (p2.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
99
99
  var formatter = (open, close, replace = open) => (input) => {
100
- let string = "" + input, index2 = string.indexOf(close, open.length);
101
- return ~index2 ? open + replaceClose(string, close, replace, index2) + close : open + string + close;
100
+ let string = "" + input, index = string.indexOf(close, open.length);
101
+ return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
102
102
  };
103
- var replaceClose = (string, close, replace, index2) => {
103
+ var replaceClose = (string, close, replace, index) => {
104
104
  let result = "", cursor3 = 0;
105
105
  do {
106
- result += string.substring(cursor3, index2) + replace;
107
- cursor3 = index2 + close.length;
108
- index2 = string.indexOf(close, cursor3);
109
- } while (~index2);
106
+ result += string.substring(cursor3, index) + replace;
107
+ cursor3 = index + close.length;
108
+ index = string.indexOf(close, cursor3);
109
+ } while (~index);
110
110
  return result + string.substring(cursor3);
111
111
  };
112
112
  var createColors = (enabled = isColorSupported) => {
@@ -202,7 +202,7 @@ async function loadConfig(cwd = process.cwd()) {
202
202
  // src/schema-loader.ts
203
203
  import { resolve as resolve2, join as join2 } from "path";
204
204
 
205
- // ../bungres-orm/src/schema/table.ts
205
+ // ../bungres-orm/dist/index.js
206
206
  var TableConfigSymbol = Symbol.for("BungresTableConfig");
207
207
  function getTableConfig(table) {
208
208
  return table[TableConfigSymbol];
@@ -272,7 +272,6 @@ var table = createTableFactory("snake");
272
272
  var snakeCase = { table: createTableFactory("snake") };
273
273
  var camelCase = { table: createTableFactory("camel") };
274
274
  var noCasing = { table: createTableFactory("none") };
275
- // ../bungres-orm/src/schema/columns.ts
276
275
  function buildColumn(dataType, nameOrOpts, opts) {
277
276
  let name = "";
278
277
  let options = opts;
@@ -301,6 +300,13 @@ function buildColumn(dataType, nameOrOpts, opts) {
301
300
  return Object.assign(config, {
302
301
  as(alias) {
303
302
  return Object.assign({}, this, { alias });
303
+ },
304
+ array() {
305
+ return Object.assign({}, this, { dataType: `${this.dataType}[]` });
306
+ },
307
+ generatedAlwaysAs(expr) {
308
+ const sqlStr = typeof expr === "string" ? expr : expr.sql;
309
+ return Object.assign({}, this, { generatedAs: sqlStr });
304
310
  }
305
311
  });
306
312
  }
@@ -330,7 +336,6 @@ var textArray = col("text[]");
330
336
  var integerArray = col("integer[]");
331
337
  var varcharArray = col("varchar[]");
332
338
  var uuidArray = col("uuid[]");
333
- // ../bungres-orm/src/core/sql.ts
334
339
  function sql(strings, ...values) {
335
340
  let query = "";
336
341
  const params = [];
@@ -373,7 +378,6 @@ function colName(c) {
373
378
  return c.sql;
374
379
  return c.tableName ? `${c.tableName}."${c.name}"` : `"${c.name}"`;
375
380
  }
376
- // ../bungres-orm/src/core/conditions.ts
377
381
  function isColumnConfig(val) {
378
382
  return val !== null && typeof val === "object" && "name" in val && "dataType" in val;
379
383
  }
@@ -527,14 +531,13 @@ function parseOrderByObject(tableConfig, orderByObj) {
527
531
  }
528
532
  return parts;
529
533
  }
530
-
531
- // ../bungres-orm/src/builders/delete.ts
532
534
  class DeleteBuilder {
533
535
  _table;
534
536
  _executor;
535
537
  _where = [];
536
538
  _returning;
537
539
  _comment;
540
+ _with = [];
538
541
  constructor(table2, executor) {
539
542
  this._table = table2;
540
543
  this._executor = executor;
@@ -553,6 +556,10 @@ class DeleteBuilder {
553
556
  }
554
557
  return this;
555
558
  }
559
+ with(...ctes) {
560
+ this._with.push(...ctes);
561
+ return this;
562
+ }
556
563
  returning(...columns) {
557
564
  this._returning = columns.length > 0 ? columns : ["*"];
558
565
  return this;
@@ -562,8 +569,20 @@ class DeleteBuilder {
562
569
  return this;
563
570
  }
564
571
  toSQL() {
565
- let query = `DELETE FROM ${getTableConfig(this._table).qualifiedName}`;
572
+ const tConfig = getTableConfig(this._table);
566
573
  const params = [];
574
+ let prefix = "";
575
+ if (this._with.length > 0) {
576
+ const cteStrs = [];
577
+ for (const cte of this._with) {
578
+ const chunk = cte.query.toSQL();
579
+ const offset = params.length;
580
+ params.push(...chunk.params);
581
+ cteStrs.push(`"${cte.alias}" AS (${chunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`)})`);
582
+ }
583
+ prefix = `WITH ${cteStrs.join(", ")} `;
584
+ }
585
+ let query = `DELETE FROM ${tConfig.qualifiedName}`;
567
586
  if (this._where.length > 0) {
568
587
  const combined = sqlJoin(this._where, " AND ");
569
588
  query += " WHERE " + combined.sql;
@@ -575,17 +594,19 @@ class DeleteBuilder {
575
594
  if (this._comment) {
576
595
  query += ` /* ${this._comment.replace(/\*\//g, "")} */`;
577
596
  }
578
- return { sql: query, params };
597
+ return { sql: prefix + query, params };
579
598
  }
580
599
  }
581
- // ../bungres-orm/src/builders/insert.ts
600
+
582
601
  class InsertBuilder {
583
602
  _table;
584
603
  _executor;
585
604
  _values = [];
586
605
  _onConflict;
606
+ _onConflictUpdateConfig;
587
607
  _returning;
588
608
  _comment;
609
+ _with = [];
589
610
  constructor(table2, executor) {
590
611
  this._table = table2;
591
612
  this._executor = executor;
@@ -612,6 +633,14 @@ class InsertBuilder {
612
633
  this._onConflict = clause;
613
634
  return this;
614
635
  }
636
+ onConflictDoUpdate(config) {
637
+ this._onConflictUpdateConfig = config;
638
+ return this;
639
+ }
640
+ with(...ctes) {
641
+ this._with.push(...ctes);
642
+ return this;
643
+ }
615
644
  returning(...columns) {
616
645
  this._returning = columns.length > 0 ? columns : ["*"];
617
646
  return this;
@@ -637,6 +666,17 @@ class InsertBuilder {
637
666
  }
638
667
  const columnsStr = keys.map((k) => `"${tConfig.columns[k]?.name ?? k}"`).join(", ");
639
668
  const params = [];
669
+ let prefix = "";
670
+ if (this._with.length > 0) {
671
+ const cteStrs = [];
672
+ for (const cte of this._with) {
673
+ const chunk = cte.query.toSQL();
674
+ const offset = params.length;
675
+ params.push(...chunk.params);
676
+ cteStrs.push(`"${cte.alias}" AS (${chunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`)})`);
677
+ }
678
+ prefix = `WITH ${cteStrs.join(", ")} `;
679
+ }
640
680
  const valuesStrs = this._values.map((v) => {
641
681
  const vals = keys.map((k) => {
642
682
  const val = v[k];
@@ -680,6 +720,52 @@ class InsertBuilder {
680
720
  query += " " + this._onConflict.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
681
721
  params.push(...this._onConflict.params);
682
722
  }
723
+ } else if (this._onConflictUpdateConfig) {
724
+ const config = this._onConflictUpdateConfig;
725
+ const targets = Array.isArray(config.target) ? config.target : [config.target];
726
+ const targetStrs = [];
727
+ for (const t of targets) {
728
+ if (typeof t === "string") {
729
+ targetStrs.push(`"${tConfig.columns[t]?.name ?? t}"`);
730
+ } else {
731
+ const offset = params.length;
732
+ targetStrs.push(t.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`));
733
+ params.push(...t.params);
734
+ }
735
+ }
736
+ const setEntries = Object.entries(config.set);
737
+ if (setEntries.length === 0)
738
+ throw new Error("InsertBuilder: onConflictDoUpdate requires 'set' fields");
739
+ const setClauses = setEntries.map(([key, value]) => {
740
+ const dbCol = tConfig.columns[key]?.name ?? key;
741
+ if (value && typeof value === "object" && "sql" in value && "params" in value) {
742
+ const chunk = value;
743
+ const offset = params.length;
744
+ params.push(...chunk.params);
745
+ return `"${dbCol}" = ${chunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`)}`;
746
+ }
747
+ if (value && typeof value === "object" && !(value instanceof Date)) {
748
+ const colType = tConfig.columns[key]?.dataType;
749
+ if (colType === "json" || colType === "jsonb") {
750
+ params.push(value);
751
+ } 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);
760
+ } else {
761
+ params.push(JSON.stringify(value));
762
+ }
763
+ return `"${dbCol}" = $${params.length}`;
764
+ }
765
+ params.push(value);
766
+ return `"${dbCol}" = $${params.length}`;
767
+ });
768
+ query += ` ON CONFLICT (${targetStrs.join(", ")}) DO UPDATE SET ${setClauses.join(", ")}`;
683
769
  }
684
770
  if (this._returning) {
685
771
  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(", "));
@@ -687,10 +773,10 @@ class InsertBuilder {
687
773
  if (this._comment) {
688
774
  query += ` /* ${this._comment.replace(/\*\//g, "")} */`;
689
775
  }
690
- return { sql: query, params };
776
+ return { sql: prefix + query, params };
691
777
  }
692
778
  }
693
- // ../bungres-orm/src/builders/select.ts
779
+
694
780
  class SelectBuilder {
695
781
  _table;
696
782
  _executor;
@@ -703,6 +789,8 @@ class SelectBuilder {
703
789
  _select;
704
790
  _selection;
705
791
  _joins = [];
792
+ _with = [];
793
+ _setOperations = [];
706
794
  _comment;
707
795
  constructor(table2, executor, selection) {
708
796
  this._table = table2;
@@ -713,12 +801,35 @@ class SelectBuilder {
713
801
  return this._executor.execute(this).then(onfulfilled, onrejected);
714
802
  }
715
803
  async single() {
804
+ if (this._limit === undefined) {
805
+ this.limit(1);
806
+ }
716
807
  return this._executor.executeSingle(this);
717
808
  }
718
809
  select(...columns) {
719
810
  this._select = columns;
720
811
  return this;
721
812
  }
813
+ with(...ctes) {
814
+ this._with.push(...ctes);
815
+ return this;
816
+ }
817
+ union(other) {
818
+ this._setOperations.push({ type: "UNION", builder: other });
819
+ return this;
820
+ }
821
+ unionAll(other) {
822
+ this._setOperations.push({ type: "UNION ALL", builder: other });
823
+ return this;
824
+ }
825
+ intersect(other) {
826
+ this._setOperations.push({ type: "INTERSECT", builder: other });
827
+ return this;
828
+ }
829
+ except(other) {
830
+ this._setOperations.push({ type: "EXCEPT", builder: other });
831
+ return this;
832
+ }
722
833
  comment(tag) {
723
834
  this._comment = tag;
724
835
  return this;
@@ -821,7 +932,23 @@ class SelectBuilder {
821
932
  }).join(", ");
822
933
  } else {
823
934
  const qName = getTableConfig(this._table).qualifiedName;
824
- cols = Object.keys(getTableConfig(this._table).columns).map((c) => `${qName}."${getTableConfig(this._table).columns[c].name}" AS "${c}"`).join(", ");
935
+ const colsKeys = Object.keys(getTableConfig(this._table).columns);
936
+ if (colsKeys.length === 0) {
937
+ cols = "*";
938
+ } else {
939
+ cols = colsKeys.map((c) => `${qName}."${getTableConfig(this._table).columns[c].name}" AS "${c}"`).join(", ");
940
+ }
941
+ }
942
+ let prefix = "";
943
+ if (this._with.length > 0) {
944
+ const cteStrs = [];
945
+ for (const cte of this._with) {
946
+ const chunk = cte.query.toSQL();
947
+ const offset = params.length;
948
+ params.push(...chunk.params);
949
+ cteStrs.push(`"${cte.alias}" AS (${chunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`)})`);
950
+ }
951
+ prefix = `WITH ${cteStrs.join(", ")} `;
825
952
  }
826
953
  let query = `SELECT ${cols} FROM ${getTableConfig(this._table).qualifiedName}`;
827
954
  if (this._joins.length > 0) {
@@ -881,10 +1008,18 @@ class SelectBuilder {
881
1008
  params.push(this._offset);
882
1009
  query += ` OFFSET $${params.length}`;
883
1010
  }
1011
+ if (this._setOperations.length > 0) {
1012
+ for (const op of this._setOperations) {
1013
+ const chunk = op.builder.toSQL();
1014
+ const offset = params.length;
1015
+ params.push(...chunk.params);
1016
+ query += ` ${op.type} ${chunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`)}`;
1017
+ }
1018
+ }
884
1019
  if (this._comment) {
885
1020
  query += ` /* ${this._comment.replace(/\*\//g, "")} */`;
886
1021
  }
887
- return { sql: query, params };
1022
+ return { sql: prefix + query, params };
888
1023
  }
889
1024
  }
890
1025
 
@@ -899,7 +1034,7 @@ class SelectBuilderIntermediate {
899
1034
  return new SelectBuilder(table2, this._executor, this._selection);
900
1035
  }
901
1036
  }
902
- // ../bungres-orm/src/builders/update.ts
1037
+
903
1038
  class UpdateBuilder {
904
1039
  _table;
905
1040
  _executor;
@@ -907,6 +1042,7 @@ class UpdateBuilder {
907
1042
  _where = [];
908
1043
  _returning;
909
1044
  _comment;
1045
+ _with = [];
910
1046
  constructor(table2, executor) {
911
1047
  this._table = table2;
912
1048
  this._executor = executor;
@@ -929,6 +1065,10 @@ class UpdateBuilder {
929
1065
  }
930
1066
  return this;
931
1067
  }
1068
+ with(...ctes) {
1069
+ this._with.push(...ctes);
1070
+ return this;
1071
+ }
932
1072
  returning(...columns) {
933
1073
  this._returning = columns.length > 0 ? columns : ["*"];
934
1074
  return this;
@@ -943,6 +1083,17 @@ class UpdateBuilder {
943
1083
  throw new Error("UpdateBuilder: no fields to set");
944
1084
  }
945
1085
  const params = [];
1086
+ let prefix = "";
1087
+ if (this._with.length > 0) {
1088
+ const cteStrs = [];
1089
+ for (const cte of this._with) {
1090
+ const chunk = cte.query.toSQL();
1091
+ const offset = params.length;
1092
+ params.push(...chunk.params);
1093
+ cteStrs.push(`"${cte.alias}" AS (${chunk.sql.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`)})`);
1094
+ }
1095
+ prefix = `WITH ${cteStrs.join(", ")} `;
1096
+ }
946
1097
  const setClauses = entries.map(([key, value]) => {
947
1098
  const dbCol = getTableConfig(this._table).columns[key]?.name ?? key;
948
1099
  if (value && typeof value === "object" && "sql" in value && "params" in value) {
@@ -985,10 +1136,9 @@ class UpdateBuilder {
985
1136
  if (this._comment) {
986
1137
  query += ` /* ${this._comment.replace(/\*\//g, "")} */`;
987
1138
  }
988
- return { sql: query, params };
1139
+ return { sql: prefix + query, params };
989
1140
  }
990
1141
  }
991
- // ../bungres-orm/src/builders/relational.ts
992
1142
  function getPkColumn(tableConfig) {
993
1143
  if (tableConfig.primaryKeys?.length > 0)
994
1144
  return tableConfig.primaryKeys[0];
@@ -1246,8 +1396,6 @@ class RelationalQueryBuilder {
1246
1396
  return { sql: sql2, params };
1247
1397
  }
1248
1398
  }
1249
-
1250
- // ../bungres-orm/src/core/db.ts
1251
1399
  function parseDBName(url) {
1252
1400
  try {
1253
1401
  return new URL(url).pathname.slice(1);
@@ -1356,6 +1504,7 @@ class BungresDB {
1356
1504
 
1357
1505
  class BungresTransaction {
1358
1506
  _sql;
1507
+ _savepointCounter = 0;
1359
1508
  constructor(sql2) {
1360
1509
  this._sql = sql2;
1361
1510
  }
@@ -1390,6 +1539,19 @@ class BungresTransaction {
1390
1539
  const result = await this._sql.unsafe(query, params);
1391
1540
  return Array.from(result);
1392
1541
  }
1542
+ async transaction(fn) {
1543
+ this._savepointCounter++;
1544
+ const spName = `sp_${this._savepointCounter}`;
1545
+ await this._sql.unsafe(`SAVEPOINT ${spName}`);
1546
+ try {
1547
+ const result = await fn(this);
1548
+ await this._sql.unsafe(`RELEASE SAVEPOINT ${spName}`);
1549
+ return result;
1550
+ } catch (e) {
1551
+ await this._sql.unsafe(`ROLLBACK TO SAVEPOINT ${spName}`);
1552
+ throw e;
1553
+ }
1554
+ }
1393
1555
  }
1394
1556
  function bungres(config) {
1395
1557
  const db = new BungresDB(config);
@@ -1413,7 +1575,6 @@ function bungres(config) {
1413
1575
  }
1414
1576
  return db;
1415
1577
  }
1416
- // ../bungres-orm/src/ddl.ts
1417
1578
  function generateCreateTable(config, ifNotExists = true) {
1418
1579
  const tableName = config.schema ? `"${config.schema}"."${config.name}"` : `"${config.name}"`;
1419
1580
  const exists = ifNotExists ? " IF NOT EXISTS" : "";
@@ -1423,8 +1584,8 @@ function generateCreateTable(config, ifNotExists = true) {
1423
1584
  columnDefs.push(`PRIMARY KEY (${pkCols})`);
1424
1585
  }
1425
1586
  if (config.checks) {
1426
- for (const check of config.checks) {
1427
- columnDefs.push(`CHECK (${check})`);
1587
+ for (const check2 of config.checks) {
1588
+ columnDefs.push(`CHECK (${check2})`);
1428
1589
  }
1429
1590
  }
1430
1591
  let sql2 = `CREATE TABLE${exists} ${tableName} (
@@ -1462,7 +1623,9 @@ function buildColumnDDL(key, col2, tableName) {
1462
1623
  if (col2.unique && !col2.primaryKey) {
1463
1624
  parts.push("UNIQUE");
1464
1625
  }
1465
- if (col2.defaultFn) {
1626
+ if (col2.generatedAs) {
1627
+ parts.push(`GENERATED ALWAYS AS (${col2.generatedAs}) STORED`);
1628
+ } else if (col2.defaultFn) {
1466
1629
  parts.push(`DEFAULT ${col2.defaultFn}`);
1467
1630
  } else if (col2.defaultValue !== undefined) {
1468
1631
  parts.push(`DEFAULT ${formatDefaultValue(col2.defaultValue, col2.dataType)}`);
@@ -1483,6 +1646,13 @@ function buildColumnDDL(key, col2, tableName) {
1483
1646
  return parts.join(" ");
1484
1647
  }
1485
1648
  function buildType(col2) {
1649
+ if (col2.enumConfig) {
1650
+ return `"${col2.enumConfig.enumName}"`;
1651
+ }
1652
+ if (col2.dataType.endsWith("[]")) {
1653
+ const baseType = buildType({ ...col2, dataType: col2.dataType.slice(0, -2) });
1654
+ return `${baseType}[]`;
1655
+ }
1486
1656
  switch (col2.dataType) {
1487
1657
  case "varchar":
1488
1658
  return col2.length ? `VARCHAR(${col2.length})` : "VARCHAR";
@@ -1517,12 +1687,12 @@ function formatDefaultValue(value, dataType) {
1517
1687
  }
1518
1688
  function buildIndex(table2, idx) {
1519
1689
  const tableName = table2.schema ? `"${table2.schema}"."${table2.name}"` : `"${table2.name}"`;
1520
- const unique = idx.unique ? "UNIQUE " : "";
1690
+ const unique2 = idx.unique ? "UNIQUE " : "";
1521
1691
  const using = idx.using ? ` USING ${idx.using.toUpperCase()}` : "";
1522
1692
  const cols = idx.columns.map((c) => `"${c}"`).join(", ");
1523
1693
  const where = idx.where ? ` WHERE ${idx.where}` : "";
1524
1694
  const idxName = idx.name ?? `idx_${table2.name}_${idx.columns.join("_")}`;
1525
- return `CREATE ${unique}INDEX IF NOT EXISTS "${idxName}" ON ${tableName}${using} (${cols})${where};`;
1695
+ return `CREATE ${unique2}INDEX IF NOT EXISTS "${idxName}" ON ${tableName}${using} (${cols})${where};`;
1526
1696
  }
1527
1697
  function generateAddColumn(tableName, schema, key, col2) {
1528
1698
  const tbl = schema ? `"${schema}"."${tableName}"` : `"${tableName}"`;
@@ -1532,6 +1702,38 @@ function generateDropColumn(tableName, schema, columnName) {
1532
1702
  const tbl = schema ? `"${schema}"."${tableName}"` : `"${tableName}"`;
1533
1703
  return `ALTER TABLE ${tbl} DROP COLUMN IF EXISTS "${columnName}";`;
1534
1704
  }
1705
+ function generateCreateEnum(name, values) {
1706
+ const vals = values.map((v) => `'${v.replace(/'/g, "''")}'`).join(", ");
1707
+ return `CREATE TYPE "${name}" AS ENUM (${vals});`;
1708
+ }
1709
+ function generateDropEnum(name, ifExists = true) {
1710
+ return `DROP TYPE${ifExists ? " IF EXISTS" : ""} "${name}";`;
1711
+ }
1712
+ function inlineParams(chunk) {
1713
+ let { sql: sql2, params } = chunk;
1714
+ return sql2.replace(/\$(\d+)/g, (_, n) => {
1715
+ const p = params[parseInt(n) - 1];
1716
+ if (typeof p === "string")
1717
+ return `'${p.replace(/'/g, "''")}'`;
1718
+ if (typeof p === "number")
1719
+ return String(p);
1720
+ if (typeof p === "boolean")
1721
+ return p ? "TRUE" : "FALSE";
1722
+ if (p === null)
1723
+ return "NULL";
1724
+ return `'${String(p).replace(/'/g, "''")}'`;
1725
+ });
1726
+ }
1727
+ function generateCreateView(view) {
1728
+ const kind = view.materialized ? "MATERIALIZED VIEW" : "VIEW";
1729
+ const inlineSql = inlineParams(view.query.toSQL());
1730
+ return `CREATE ${kind} "${view.name}" AS ${inlineSql};`;
1731
+ }
1732
+ function generateDropView(view, ifExists = true) {
1733
+ const kind = view.materialized ? "MATERIALIZED VIEW" : "VIEW";
1734
+ return `DROP ${kind}${ifExists ? " IF EXISTS" : ""} "${view.name}";`;
1735
+ }
1736
+
1535
1737
  // src/schema-loader.ts
1536
1738
  async function loadSchemas(patterns, cwd = process.cwd()) {
1537
1739
  const globs = Array.isArray(patterns) ? patterns : [patterns];
@@ -1544,11 +1746,27 @@ async function loadSchemas(patterns, cwd = process.cwd()) {
1544
1746
  for (const [exportName, value] of Object.entries(mod)) {
1545
1747
  if (isTable(value)) {
1546
1748
  entries.push({
1749
+ type: "table",
1547
1750
  exportName,
1548
1751
  config: value[TableConfigSymbol],
1549
1752
  table: value,
1550
1753
  filePath: absPath
1551
1754
  });
1755
+ } else if (isEnum(value)) {
1756
+ entries.push({
1757
+ type: "enum",
1758
+ exportName,
1759
+ enumName: value.enumName,
1760
+ enumValues: value.enumValues,
1761
+ filePath: absPath
1762
+ });
1763
+ } else if (isView(value)) {
1764
+ entries.push({
1765
+ type: "view",
1766
+ exportName,
1767
+ config: value,
1768
+ filePath: absPath
1769
+ });
1552
1770
  }
1553
1771
  }
1554
1772
  }
@@ -1558,17 +1776,67 @@ async function loadSchemas(patterns, cwd = process.cwd()) {
1558
1776
  function isTable(value) {
1559
1777
  return typeof value === "object" && value !== null && TableConfigSymbol in value;
1560
1778
  }
1779
+ function isEnum(value) {
1780
+ return typeof value === "function" && "enumName" in value && "enumValues" in value && Array.isArray(value.enumValues);
1781
+ }
1782
+ function isView(value) {
1783
+ return typeof value === "object" && value !== null && "name" in value && "query" in value && typeof value.query === "object" && typeof value.query.toSQL === "function";
1784
+ }
1561
1785
  // src/differ.ts
1562
1786
  function diffSchemas(prev, next) {
1563
1787
  const statements = [];
1564
1788
  const summary = [];
1565
1789
  const warnings = [];
1566
- const prevTables = new Set(Object.keys(prev));
1567
- const nextTables = new Set(Object.keys(next));
1790
+ const prevEnums = prev.enums || {};
1791
+ const nextEnums = next.enums || {};
1792
+ const prevEnumNames = new Set(Object.keys(prevEnums));
1793
+ const nextEnumNames = new Set(Object.keys(nextEnums));
1794
+ for (const enumName of nextEnumNames) {
1795
+ if (!prevEnumNames.has(enumName)) {
1796
+ const e = nextEnums[enumName];
1797
+ statements.push(generateCreateEnum(e.enumName, e.enumValues));
1798
+ summary.push(`CREATE TYPE ${e.enumName}`);
1799
+ }
1800
+ }
1801
+ for (const enumName of prevEnumNames) {
1802
+ if (!nextEnumNames.has(enumName)) {
1803
+ const e = prevEnums[enumName];
1804
+ statements.push(generateDropEnum(e.enumName, true));
1805
+ summary.push(`DROP TYPE ${e.enumName}`);
1806
+ }
1807
+ }
1808
+ for (const enumName of nextEnumNames) {
1809
+ if (prevEnumNames.has(enumName)) {
1810
+ const p = prevEnums[enumName];
1811
+ const n = nextEnums[enumName];
1812
+ if (JSON.stringify(p.enumValues) !== JSON.stringify(n.enumValues)) {
1813
+ warnings.push(`Enum '${n.enumName}' values have changed. Bungres Kit currently requires manual migration for ALTER TYPE ... ADD VALUE.`);
1814
+ }
1815
+ }
1816
+ }
1817
+ const prevViews = prev.views || {};
1818
+ const nextViews = next.views || {};
1819
+ const prevViewNames = new Set(Object.keys(prevViews));
1820
+ const nextViewNames = new Set(Object.keys(nextViews));
1821
+ for (const viewName of prevViewNames) {
1822
+ if (!nextViewNames.has(viewName)) {
1823
+ statements.push(generateDropView(prevViews[viewName]));
1824
+ summary.push(`DROP VIEW ${viewName}`);
1825
+ } else {
1826
+ const pSql = generateCreateView(prevViews[viewName]);
1827
+ const nSql = generateCreateView(nextViews[viewName]);
1828
+ if (pSql !== nSql) {
1829
+ statements.push(generateDropView(prevViews[viewName]));
1830
+ summary.push(`DROP VIEW ${viewName} (for recreation)`);
1831
+ }
1832
+ }
1833
+ }
1834
+ const prevTables = new Set(Object.keys(prev.tables || {}));
1835
+ const nextTables = new Set(Object.keys(next.tables || {}));
1568
1836
  const newTableConfigs = [];
1569
1837
  for (const tableName of nextTables) {
1570
1838
  if (!prevTables.has(tableName)) {
1571
- newTableConfigs.push(next[tableName]);
1839
+ newTableConfigs.push(next.tables[tableName]);
1572
1840
  }
1573
1841
  }
1574
1842
  for (const config of topoSortConfigs(newTableConfigs)) {
@@ -1577,7 +1845,7 @@ function diffSchemas(prev, next) {
1577
1845
  }
1578
1846
  for (const tableName of prevTables) {
1579
1847
  if (!nextTables.has(tableName)) {
1580
- const config = prev[tableName];
1848
+ const config = prev.tables[tableName];
1581
1849
  const tbl = config.schema ? `"${config.schema}"."${tableName}"` : `"${tableName}"`;
1582
1850
  statements.push(`DROP TABLE IF EXISTS ${tbl};`);
1583
1851
  summary.push(`DROP TABLE ${tableName}`);
@@ -1587,8 +1855,8 @@ function diffSchemas(prev, next) {
1587
1855
  for (const tableName of nextTables) {
1588
1856
  if (!prevTables.has(tableName))
1589
1857
  continue;
1590
- const prevConfig = prev[tableName];
1591
- const nextConfig = next[tableName];
1858
+ const prevConfig = prev.tables[tableName];
1859
+ const nextConfig = next.tables[tableName];
1592
1860
  const prevCols = prevConfig.columns;
1593
1861
  const nextCols = nextConfig.columns;
1594
1862
  const prevColNames = new Set(Object.keys(prevCols));
@@ -1643,11 +1911,11 @@ function diffSchemas(prev, next) {
1643
1911
  const idxName = idx.name ?? `idx_${tableName}_${idx.columns.join("_")}`;
1644
1912
  if (!prevIdxNames.has(idxName)) {
1645
1913
  const tbl = nextConfig.schema ? `"${nextConfig.schema}"."${tableName}"` : `"${tableName}"`;
1646
- const unique2 = idx.unique ? "UNIQUE " : "";
1914
+ const unique = idx.unique ? "UNIQUE " : "";
1647
1915
  const using = idx.using ? ` USING ${idx.using.toUpperCase()}` : "";
1648
1916
  const cols = idx.columns.map((c) => `"${c}"`).join(", ");
1649
1917
  const where = idx.where ? ` WHERE ${idx.where}` : "";
1650
- statements.push(`CREATE ${unique2}INDEX IF NOT EXISTS "${idxName}" ON ${tbl}${using} (${cols})${where};`);
1918
+ statements.push(`CREATE ${unique}INDEX IF NOT EXISTS "${idxName}" ON ${tbl}${using} (${cols})${where};`);
1651
1919
  summary.push(`CREATE INDEX ${idxName} ON ${tableName}`);
1652
1920
  }
1653
1921
  }
@@ -1660,6 +1928,19 @@ function diffSchemas(prev, next) {
1660
1928
  }
1661
1929
  }
1662
1930
  }
1931
+ for (const viewName of nextViewNames) {
1932
+ if (!prevViewNames.has(viewName)) {
1933
+ statements.push(generateCreateView(nextViews[viewName]));
1934
+ summary.push(`CREATE VIEW ${viewName}`);
1935
+ } else {
1936
+ const pSql = generateCreateView(prevViews[viewName]);
1937
+ const nSql = generateCreateView(nextViews[viewName]);
1938
+ if (pSql !== nSql) {
1939
+ statements.push(generateCreateView(nextViews[viewName]));
1940
+ summary.push(`CREATE VIEW ${viewName} (recreated)`);
1941
+ }
1942
+ }
1943
+ }
1663
1944
  return { statements, summary, warnings };
1664
1945
  }
1665
1946
  function topoSortConfigs(tables) {
@@ -1687,7 +1968,14 @@ function topoSortConfigs(tables) {
1687
1968
  function diffColumn(prev, next) {
1688
1969
  const changes = [];
1689
1970
  const col2 = `"${next.name}"`;
1971
+ const prevDef = prev.defaultFn ?? (prev.defaultValue !== undefined ? String(prev.defaultValue) : undefined);
1972
+ const nextDef = next.defaultFn ?? (next.defaultValue !== undefined ? String(next.defaultValue) : undefined);
1973
+ let typeChanged = false;
1690
1974
  if (prev.dataType !== next.dataType) {
1975
+ typeChanged = true;
1976
+ if (prevDef !== undefined) {
1977
+ changes.push(`ALTER COLUMN ${col2} DROP DEFAULT`);
1978
+ }
1691
1979
  changes.push(`ALTER COLUMN ${col2} TYPE ${next.dataType.toUpperCase()} USING ${col2}::${next.dataType}`);
1692
1980
  } else {
1693
1981
  const prevLen = prev.length;
@@ -1704,9 +1992,12 @@ function diffColumn(prev, next) {
1704
1992
  if (prev.notNull && !next.notNull) {
1705
1993
  changes.push(`ALTER COLUMN ${col2} DROP NOT NULL`);
1706
1994
  }
1707
- const prevDef = prev.defaultFn ?? (prev.defaultValue !== undefined ? String(prev.defaultValue) : undefined);
1708
- const nextDef = next.defaultFn ?? (next.defaultValue !== undefined ? String(next.defaultValue) : undefined);
1709
- if (prevDef !== nextDef) {
1995
+ if (typeChanged) {
1996
+ if (nextDef !== undefined) {
1997
+ const val = next.defaultFn ?? formatDefault(next.defaultValue, next.dataType);
1998
+ changes.push(`ALTER COLUMN ${col2} SET DEFAULT ${val}`);
1999
+ }
2000
+ } else if (prevDef !== nextDef) {
1710
2001
  if (nextDef !== undefined) {
1711
2002
  const val = next.defaultFn ?? formatDefault(next.defaultValue, next.dataType);
1712
2003
  changes.push(`ALTER COLUMN ${col2} SET DEFAULT ${val}`);
@@ -1785,7 +2076,7 @@ var getStringTruncatedWidth = (input, truncationOptions = {}, widthOptions = {})
1785
2076
  [CJKT_WIDE_RE, WIDE_WIDTH]
1786
2077
  ];
1787
2078
  let indexPrev = 0;
1788
- let index2 = 0;
2079
+ let index = 0;
1789
2080
  let length = input.length;
1790
2081
  let lengthExtra = 0;
1791
2082
  let truncationEnabled = false;
@@ -1797,8 +2088,8 @@ var getStringTruncatedWidth = (input, truncationOptions = {}, widthOptions = {})
1797
2088
  let widthExtra = 0;
1798
2089
  outer:
1799
2090
  while (true) {
1800
- if (unmatchedEnd > unmatchedStart || index2 >= length && index2 > indexPrev) {
1801
- const unmatched = input.slice(unmatchedStart, unmatchedEnd) || input.slice(indexPrev, index2);
2091
+ if (unmatchedEnd > unmatchedStart || index >= length && index > indexPrev) {
2092
+ const unmatched = input.slice(unmatchedStart, unmatchedEnd) || input.slice(indexPrev, index);
1802
2093
  lengthExtra = 0;
1803
2094
  for (const char of unmatched.replaceAll(MODIFIER_RE, "")) {
1804
2095
  const codePoint = char.codePointAt(0) || 0;
@@ -1821,17 +2112,17 @@ var getStringTruncatedWidth = (input, truncationOptions = {}, widthOptions = {})
1821
2112
  }
1822
2113
  unmatchedStart = unmatchedEnd = 0;
1823
2114
  }
1824
- if (index2 >= length) {
2115
+ if (index >= length) {
1825
2116
  break outer;
1826
2117
  }
1827
2118
  for (let i = 0, l = PARSE_BLOCKS.length;i < l; i++) {
1828
2119
  const [BLOCK_RE, BLOCK_WIDTH] = PARSE_BLOCKS[i];
1829
- BLOCK_RE.lastIndex = index2;
2120
+ BLOCK_RE.lastIndex = index;
1830
2121
  if (BLOCK_RE.test(input)) {
1831
- lengthExtra = BLOCK_RE === CJKT_WIDE_RE ? getCodePointsLength(input.slice(index2, BLOCK_RE.lastIndex)) : BLOCK_RE === EMOJI_RE ? 1 : BLOCK_RE.lastIndex - index2;
2122
+ lengthExtra = BLOCK_RE === CJKT_WIDE_RE ? getCodePointsLength(input.slice(index, BLOCK_RE.lastIndex)) : BLOCK_RE === EMOJI_RE ? 1 : BLOCK_RE.lastIndex - index;
1832
2123
  widthExtra = lengthExtra * BLOCK_WIDTH;
1833
2124
  if (width + widthExtra > truncationLimit) {
1834
- truncationIndex = Math.min(truncationIndex, index2 + Math.floor((truncationLimit - width) / BLOCK_WIDTH));
2125
+ truncationIndex = Math.min(truncationIndex, index + Math.floor((truncationLimit - width) / BLOCK_WIDTH));
1835
2126
  }
1836
2127
  if (width + widthExtra > LIMIT) {
1837
2128
  truncationEnabled = true;
@@ -1839,12 +2130,12 @@ var getStringTruncatedWidth = (input, truncationOptions = {}, widthOptions = {})
1839
2130
  }
1840
2131
  width += widthExtra;
1841
2132
  unmatchedStart = indexPrev;
1842
- unmatchedEnd = index2;
1843
- index2 = indexPrev = BLOCK_RE.lastIndex;
2133
+ unmatchedEnd = index;
2134
+ index = indexPrev = BLOCK_RE.lastIndex;
1844
2135
  continue outer;
1845
2136
  }
1846
2137
  }
1847
- index2 += 1;
2138
+ index += 1;
1848
2139
  }
1849
2140
  return {
1850
2141
  width: truncationEnabled ? truncationLimit : width,
@@ -1903,7 +2194,7 @@ var getClosingCode = (openingCode) => {
1903
2194
  };
1904
2195
  var wrapAnsiCode = (code) => `${ESC}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`;
1905
2196
  var wrapAnsiHyperlink = (url) => `${ESC}${ANSI_ESCAPE_LINK}${url}${ANSI_ESCAPE_BELL}`;
1906
- var wrapWord = (rows, word, columns2) => {
2197
+ var wrapWord = (rows, word, columns) => {
1907
2198
  const characters = word[Symbol.iterator]();
1908
2199
  let isInsideEscape = false;
1909
2200
  let isInsideLinkEscape = false;
@@ -1915,7 +2206,7 @@ var wrapWord = (rows, word, columns2) => {
1915
2206
  while (!currentCharacter.done) {
1916
2207
  const character = currentCharacter.value;
1917
2208
  const characterLength = dist_default2(character);
1918
- if (visible + characterLength <= columns2) {
2209
+ if (visible + characterLength <= columns) {
1919
2210
  rows[rows.length - 1] += character;
1920
2211
  } else {
1921
2212
  rows.push(character);
@@ -1936,7 +2227,7 @@ var wrapWord = (rows, word, columns2) => {
1936
2227
  }
1937
2228
  } else {
1938
2229
  visible += characterLength;
1939
- if (visible === columns2 && !nextCharacter.done) {
2230
+ if (visible === columns && !nextCharacter.done) {
1940
2231
  rows.push("");
1941
2232
  visible = 0;
1942
2233
  }
@@ -1964,7 +2255,7 @@ var stringVisibleTrimSpacesRight = (string) => {
1964
2255
  }
1965
2256
  return words.slice(0, last).join(" ") + words.slice(last).join("");
1966
2257
  };
1967
- var exec = (string, columns2, options = {}) => {
2258
+ var exec = (string, columns, options = {}) => {
1968
2259
  if (options.trim !== false && string.trim() === "") {
1969
2260
  return "";
1970
2261
  }
@@ -1974,8 +2265,8 @@ var exec = (string, columns2, options = {}) => {
1974
2265
  const words = string.split(" ");
1975
2266
  let rows = [""];
1976
2267
  let rowLength = 0;
1977
- for (let index2 = 0;index2 < words.length; index2++) {
1978
- const word = words[index2];
2268
+ for (let index = 0;index < words.length; index++) {
2269
+ const word = words[index];
1979
2270
  if (options.trim !== false) {
1980
2271
  const row = rows.at(-1) ?? "";
1981
2272
  const trimmed = row.trimStart();
@@ -1984,8 +2275,8 @@ var exec = (string, columns2, options = {}) => {
1984
2275
  rowLength = dist_default2(trimmed);
1985
2276
  }
1986
2277
  }
1987
- if (index2 !== 0) {
1988
- if (rowLength >= columns2 && (options.wordWrap === false || options.trim === false)) {
2278
+ if (index !== 0) {
2279
+ if (rowLength >= columns && (options.wordWrap === false || options.trim === false)) {
1989
2280
  rows.push("");
1990
2281
  rowLength = 0;
1991
2282
  }
@@ -1995,28 +2286,28 @@ var exec = (string, columns2, options = {}) => {
1995
2286
  }
1996
2287
  }
1997
2288
  const wordLength = dist_default2(word);
1998
- if (options.hard && wordLength > columns2) {
1999
- const remainingColumns = columns2 - rowLength;
2000
- const breaksStartingThisLine = 1 + Math.floor((wordLength - remainingColumns - 1) / columns2);
2001
- const breaksStartingNextLine = Math.floor((wordLength - 1) / columns2);
2289
+ if (options.hard && wordLength > columns) {
2290
+ const remainingColumns = columns - rowLength;
2291
+ const breaksStartingThisLine = 1 + Math.floor((wordLength - remainingColumns - 1) / columns);
2292
+ const breaksStartingNextLine = Math.floor((wordLength - 1) / columns);
2002
2293
  if (breaksStartingNextLine < breaksStartingThisLine) {
2003
2294
  rows.push("");
2004
2295
  }
2005
- wrapWord(rows, word, columns2);
2296
+ wrapWord(rows, word, columns);
2006
2297
  rowLength = dist_default2(rows.at(-1) ?? "");
2007
2298
  continue;
2008
2299
  }
2009
- if (rowLength + wordLength > columns2 && rowLength && wordLength) {
2010
- if (options.wordWrap === false && rowLength < columns2) {
2011
- wrapWord(rows, word, columns2);
2300
+ if (rowLength + wordLength > columns && rowLength && wordLength) {
2301
+ if (options.wordWrap === false && rowLength < columns) {
2302
+ wrapWord(rows, word, columns);
2012
2303
  rowLength = dist_default2(rows.at(-1) ?? "");
2013
2304
  continue;
2014
2305
  }
2015
2306
  rows.push("");
2016
2307
  rowLength = 0;
2017
2308
  }
2018
- if (rowLength + wordLength > columns2 && options.wordWrap === false) {
2019
- wrapWord(rows, word, columns2);
2309
+ if (rowLength + wordLength > columns && options.wordWrap === false) {
2310
+ wrapWord(rows, word, columns);
2020
2311
  rowLength = dist_default2(rows.at(-1) ?? "");
2021
2312
  continue;
2022
2313
  }
@@ -2073,8 +2364,8 @@ var exec = (string, columns2, options = {}) => {
2073
2364
  return returnValue;
2074
2365
  };
2075
2366
  var CRLF_OR_LF = /\r?\n/;
2076
- function wrapAnsi(string, columns2, options) {
2077
- return String(string).normalize().split(CRLF_OR_LF).map((line) => exec(line, columns2, options)).join(`
2367
+ function wrapAnsi(string, columns, options) {
2368
+ return String(string).normalize().split(CRLF_OR_LF).map((line) => exec(line, columns, options)).join(`
2078
2369
  `);
2079
2370
  }
2080
2371
 
@@ -3117,11 +3408,29 @@ async function runPush(config, opts = {}) {
3117
3408
  );
3118
3409
  `);
3119
3410
  const rows = await sql2.unsafe(`SELECT snapshot FROM ${qualifiedPush} ORDER BY id DESC LIMIT 1;`);
3120
- let prevSnapshot = {};
3411
+ let prevSnapshot = { tables: {}, enums: {}, views: {} };
3121
3412
  if (rows.length > 0) {
3122
- prevSnapshot = typeof rows[0].snapshot === "string" ? JSON.parse(rows[0].snapshot) : rows[0].snapshot;
3413
+ const parsed = typeof rows[0].snapshot === "string" ? JSON.parse(rows[0].snapshot) : rows[0].snapshot;
3414
+ if (parsed.tables || parsed.enums || parsed.views) {
3415
+ prevSnapshot = {
3416
+ tables: parsed.tables || {},
3417
+ enums: parsed.enums || {},
3418
+ views: parsed.views || {}
3419
+ };
3420
+ } else {
3421
+ prevSnapshot = { tables: parsed, enums: {}, views: {} };
3422
+ }
3423
+ }
3424
+ 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;
3432
+ }
3123
3433
  }
3124
- const currentSnapshot = Object.fromEntries(schemas.map((schemaEntry) => [schemaEntry.config.name, schemaEntry.config]));
3125
3434
  const diff = diffSchemas(prevSnapshot, currentSnapshot);
3126
3435
  if (diff.statements.length === 0) {
3127
3436
  ms.stop("Up to date.");
@@ -3194,19 +3503,46 @@ async function runGenerate(config, name) {
3194
3503
  const metaDir = join3(migrationsDir, "meta");
3195
3504
  await Bun.$`mkdir -p ${migrationsDir}`.quiet();
3196
3505
  await Bun.$`mkdir -p ${metaDir}`.quiet();
3197
- const currentSnapshot = Object.fromEntries(schemas.map((s2) => [s2.config.name, s2.config]));
3198
- let prevSnapshot = {};
3506
+ const currentSnapshot = { tables: {}, enums: {}, views: {} };
3507
+ for (const s2 of schemas) {
3508
+ if (s2.type === "table") {
3509
+ currentSnapshot.tables[s2.config.name] = s2.config;
3510
+ } else if (s2.type === "enum") {
3511
+ currentSnapshot.enums[s2.enumName] = { enumName: s2.enumName, enumValues: s2.enumValues };
3512
+ } else if (s2.type === "view") {
3513
+ currentSnapshot.views[s2.config.name] = s2.config;
3514
+ }
3515
+ }
3516
+ let prevSnapshot = { tables: {}, enums: {}, views: {} };
3199
3517
  let isFirstMigration = true;
3200
3518
  try {
3201
3519
  const files = readdirSync(metaDir).filter((f2) => f2.endsWith("_snapshot.json")).sort();
3202
3520
  if (files.length > 0) {
3203
3521
  const latest = files[files.length - 1];
3204
- prevSnapshot = JSON.parse(await Bun.file(join3(metaDir, latest)).text());
3522
+ const parsed = JSON.parse(await Bun.file(join3(metaDir, latest)).text());
3523
+ if (parsed.tables || parsed.enums || parsed.views) {
3524
+ prevSnapshot = {
3525
+ tables: parsed.tables || {},
3526
+ enums: parsed.enums || {},
3527
+ views: parsed.views || {}
3528
+ };
3529
+ } else {
3530
+ prevSnapshot = { tables: parsed, enums: {}, views: {} };
3531
+ }
3205
3532
  isFirstMigration = false;
3206
3533
  } else {
3207
3534
  const legacyFile2 = Bun.file(join3(migrationsDir, ".snapshot.json"));
3208
3535
  if (await legacyFile2.exists()) {
3209
- prevSnapshot = JSON.parse(await legacyFile2.text());
3536
+ const parsed = JSON.parse(await legacyFile2.text());
3537
+ if (parsed.tables || parsed.enums || parsed.views) {
3538
+ prevSnapshot = {
3539
+ tables: parsed.tables || {},
3540
+ enums: parsed.enums || {},
3541
+ views: parsed.views || {}
3542
+ };
3543
+ } else {
3544
+ prevSnapshot = { tables: parsed, enums: {}, views: {} };
3545
+ }
3210
3546
  isFirstMigration = false;
3211
3547
  }
3212
3548
  }
@@ -3226,17 +3562,30 @@ async function runGenerate(config, name) {
3226
3562
  let summary;
3227
3563
  let warnings = [];
3228
3564
  if (isFirstMigration) {
3229
- const sorted = topoSort(schemas);
3230
- upStatements = sorted.flatMap((entry) => [
3231
- `-- ${entry.exportName}`,
3232
- generateCreateTable(entry.config, true),
3233
- ``
3234
- ]);
3235
- downStatements = [...sorted].reverse().flatMap((entry) => {
3565
+ const tableSchemas = schemas.filter((s2) => s2.type === "table");
3566
+ const enumSchemas = schemas.filter((s2) => s2.type === "enum");
3567
+ const viewSchemas = schemas.filter((s2) => s2.type === "view");
3568
+ const sorted = topoSort(tableSchemas);
3569
+ upStatements = [];
3570
+ downStatements = [];
3571
+ summary = [];
3572
+ for (const e of enumSchemas) {
3573
+ upStatements.push(`-- ${e.exportName}`, `CREATE TYPE "${e.enumName}" AS ENUM (${e.enumValues.map((v) => `'${v.replace(/'/g, "''")}'`).join(", ")});`, ``);
3574
+ downStatements.unshift(`DROP TYPE IF EXISTS "${e.enumName}";`);
3575
+ summary.push(`CREATE TYPE ${e.enumName}`);
3576
+ }
3577
+ for (const entry of sorted) {
3578
+ upStatements.push(`-- ${entry.exportName}`, generateCreateTable(entry.config, true), ``);
3236
3579
  const tbl = entry.config.schema ? `"${entry.config.schema}"."${entry.config.name}"` : `"${entry.config.name}"`;
3237
- return [`DROP TABLE IF EXISTS ${tbl};`];
3238
- });
3239
- summary = sorted.map((s2) => `CREATE TABLE ${s2.config.name}`);
3580
+ downStatements.unshift(`DROP TABLE IF EXISTS ${tbl};`);
3581
+ summary.push(`CREATE TABLE ${entry.config.name}`);
3582
+ }
3583
+ for (const v of viewSchemas) {
3584
+ upStatements.push(`-- ${v.exportName}`, generateCreateView(v.config), ``);
3585
+ const isMat = v.config.materialized ? "MATERIALIZED VIEW" : "VIEW";
3586
+ downStatements.unshift(`DROP ${isMat} IF EXISTS "${v.config.name}";`);
3587
+ summary.push(`CREATE ${isMat} ${v.config.name}`);
3588
+ }
3240
3589
  } else {
3241
3590
  const upDiff = diffSchemas(prevSnapshot, currentSnapshot);
3242
3591
  if (upDiff.statements.length === 0) {
@@ -3398,7 +3747,7 @@ ${upContent}
3398
3747
  // src/commands/pull.ts
3399
3748
  import { resolve as resolve5, join as join5 } from "path";
3400
3749
  async function introspectDb(sql2, dbSchema) {
3401
- const columns2 = await sql2.unsafe(`SELECT
3750
+ const columns = await sql2.unsafe(`SELECT
3402
3751
  c.table_name,
3403
3752
  c.column_name,
3404
3753
  c.data_type,
@@ -3432,7 +3781,7 @@ async function introspectDb(sql2, dbSchema) {
3432
3781
  const indexes = await sql2.unsafe(`SELECT tablename, indexname, indexdef
3433
3782
  FROM pg_indexes
3434
3783
  WHERE schemaname = $1`, [dbSchema]);
3435
- return groupByTable(columns2, constraints, indexes);
3784
+ return groupByTable(columns, constraints, indexes);
3436
3785
  }
3437
3786
  async function runPull(config) {
3438
3787
  console.log("@bungres/kit pull: introspecting database...");
@@ -3455,9 +3804,9 @@ async function runPull(config) {
3455
3804
  await sql2.end();
3456
3805
  }
3457
3806
  }
3458
- function groupByTable(columns2, constraints, indexes) {
3807
+ function groupByTable(columns, constraints, indexes) {
3459
3808
  const map = new Map;
3460
- for (const col2 of columns2) {
3809
+ for (const col2 of columns) {
3461
3810
  if (!map.has(col2.table_name)) {
3462
3811
  map.set(col2.table_name, {
3463
3812
  tableName: col2.table_name,
@@ -3493,19 +3842,18 @@ function generateSchemaTS(tableMap, dbSchema) {
3493
3842
  `// Generated at: ${new Date().toISOString()}`,
3494
3843
  ``,
3495
3844
  `import {`,
3496
- ` table,`,
3845
+ ` pgTable,`,
3497
3846
  ` text, varchar, char, integer, bigint, smallint,`,
3498
3847
  ` serial, bigserial, boolean, real, doublePrecision,`,
3499
3848
  ` numeric, decimal, json, jsonb,`,
3500
3849
  ` timestamp, timestamptz, date, time, uuid,`,
3501
3850
  ` bytea, inet,`,
3502
- ` textArray, integerArray, varcharArray, uuidArray,`,
3503
3851
  `} from "@bungres/orm";`,
3504
3852
  ``
3505
3853
  ];
3506
3854
  for (const [, table2] of tableMap) {
3507
3855
  const varName = toCamelCase(table2.tableName);
3508
- lines.push(`export const ${varName} = table("${table2.tableName}", {`);
3856
+ lines.push(`export const ${varName} = pgTable("${table2.tableName}", {`);
3509
3857
  for (const col2 of table2.columns) {
3510
3858
  const colExpr = buildColumnExpression(col2);
3511
3859
  lines.push(` ${toCamelCase(col2.name)}: ${colExpr},`);
@@ -3586,11 +3934,13 @@ function buildColumnExpression(col2) {
3586
3934
  refOpts += `, onUpdate: "${updateRule}"`;
3587
3935
  opts.push(`references: { ${refOpts} }`);
3588
3936
  }
3589
- let builderName = pgTypeToBungresBuilderName(col2);
3590
- if (opts.length > 0) {
3591
- return `${builderName}({ ${opts.join(", ")} })`;
3592
- }
3593
- return `${builderName}()`;
3937
+ let builderResult = pgTypeToBungresBuilderName(col2);
3938
+ let builderName = typeof builderResult === "string" ? builderResult : builderResult.base;
3939
+ let isArray = typeof builderResult !== "string" && builderResult.isArray;
3940
+ let expr = opts.length > 0 ? `${builderName}({ ${opts.join(", ")} })` : `${builderName}()`;
3941
+ if (isArray)
3942
+ expr += `.array()`;
3943
+ return expr;
3594
3944
  }
3595
3945
  function pgTypeToBungresBuilderName(col2) {
3596
3946
  const dt = col2.dataType;
@@ -3636,14 +3986,14 @@ function pgTypeToBungresBuilderName(col2) {
3636
3986
  return "text";
3637
3987
  if (dt === "ARRAY") {
3638
3988
  if (col2.udtName === "_text")
3639
- return "textArray";
3989
+ return { base: "text", isArray: true };
3640
3990
  if (col2.udtName === "_int4" || col2.udtName === "_int8")
3641
- return "integerArray";
3991
+ return { base: "integer", isArray: true };
3642
3992
  if (col2.udtName === "_varchar")
3643
- return "varcharArray";
3993
+ return { base: "varchar", isArray: true };
3644
3994
  if (col2.udtName === "_uuid")
3645
- return "uuidArray";
3646
- return "textArray";
3995
+ return { base: "uuid", isArray: true };
3996
+ return { base: "text", isArray: true };
3647
3997
  }
3648
3998
  return "text";
3649
3999
  }
@@ -3714,9 +4064,12 @@ async function runDrop(config, opts = {}) {
3714
4064
  const s = spinner();
3715
4065
  s.start("Loading schemas...");
3716
4066
  const schemas = await loadSchemas(config.schema);
3717
- if (schemas.length === 0) {
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) {
3718
4071
  s.stop("No schemas found.");
3719
- log.warn(import_picocolors5.default.yellow("No table definitions found in schema files."));
4072
+ log.warn(import_picocolors5.default.yellow("No definitions found in schema files."));
3720
4073
  outro("Failed.");
3721
4074
  return;
3722
4075
  }
@@ -3738,25 +4091,31 @@ async function runDrop(config, opts = {}) {
3738
4091
  WHERE table_schema = $1 AND table_name = $2
3739
4092
  ) AS exists`, [config.migrationsSchema, "__bungres_push"]);
3740
4093
  const pushTableExists = pushTableCheck[0]?.exists ?? false;
3741
- const tablesToDrop = schemas.filter((sch) => existingTableNames.has(sch.config.name));
3742
- if (tablesToDrop.length === 0 && !migrationTableExists && !pushTableExists) {
4094
+ const tablesToDrop = tableSchemas.filter((sch) => existingTableNames.has(sch.config.name));
4095
+ if (tablesToDrop.length === 0 && enumSchemas.length === 0 && viewSchemas.length === 0 && !migrationTableExists && !pushTableExists) {
3743
4096
  ms.stop("Nothing to do.");
3744
- log.success(import_picocolors5.default.green("No tables to drop (they either don't exist or were already dropped)."));
4097
+ log.success(import_picocolors5.default.green("No items to drop (they either don't exist or were already dropped)."));
3745
4098
  outro("Done.");
3746
4099
  return;
3747
4100
  }
3748
4101
  ms.stop("Database check complete.");
3749
- const tableNamesToPrint = tablesToDrop.map((sch) => (sch.config.schema ? sch.config.schema + "." : "") + sch.config.name);
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
+ }
3750
4109
  if (migrationTableExists) {
3751
- tableNamesToPrint.push(`${config.migrationsSchema}.${config.migrationsTable}`);
4110
+ namesToPrint.push(`${config.migrationsSchema}.${config.migrationsTable} (table)`);
3752
4111
  }
3753
4112
  if (pushTableExists) {
3754
- tableNamesToPrint.push(`${config.migrationsSchema}.__bungres_push`);
4113
+ namesToPrint.push(`${config.migrationsSchema}.__bungres_push (table)`);
3755
4114
  }
3756
4115
  if (!opts.force) {
3757
4116
  log.warn(import_picocolors5.default.bgRed(import_picocolors5.default.white(" \u26A0\uFE0F WARNING ")));
3758
- log.message(import_picocolors5.default.bold(import_picocolors5.default.red("This will drop the following tables and ALL their data:")));
3759
- for (const name of tableNamesToPrint) {
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) {
3760
4119
  log.info(import_picocolors5.default.yellow(` - ${name}`));
3761
4120
  }
3762
4121
  const confirm2 = await confirm({
@@ -3770,11 +4129,22 @@ async function runDrop(config, opts = {}) {
3770
4129
  }
3771
4130
  const exSpinner = spinner();
3772
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
+ }
3773
4138
  for (const entry of tablesToDrop) {
3774
4139
  const ddl = `DROP TABLE IF EXISTS "${entry.config.name}" CASCADE;`;
3775
4140
  await sql2.unsafe(ddl);
3776
4141
  log.step(import_picocolors5.default.gray(`Dropped ${entry.config.name}`));
3777
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}`));
4147
+ }
3778
4148
  if (migrationTableExists) {
3779
4149
  const qualifiedMigTable = `"${config.migrationsSchema}"."${config.migrationsTable}"`;
3780
4150
  await sql2.unsafe(`DROP TABLE IF EXISTS ${qualifiedMigTable} CASCADE`);
@@ -3784,7 +4154,7 @@ async function runDrop(config, opts = {}) {
3784
4154
  await sql2.unsafe(`DROP TABLE IF EXISTS "${config.migrationsSchema}"."__bungres_push" CASCADE`);
3785
4155
  log.step(import_picocolors5.default.gray(`Dropped ${config.migrationsSchema}.__bungres_push`));
3786
4156
  }
3787
- exSpinner.stop("Tables dropped.");
4157
+ exSpinner.stop("Items dropped.");
3788
4158
  outro(import_picocolors5.default.cyan("\u2728 Drop complete."));
3789
4159
  } catch (err) {
3790
4160
  log.error(import_picocolors5.default.red(`Drop failed: ${err.message}`));