@bungres/kit 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -70,9 +70,10 @@ function createTableFactory(casing) {
70
70
  return Object.assign(tableObj, columnConfigs);
71
71
  };
72
72
  }
73
- var table = createTableFactory("none");
73
+ var table = createTableFactory("snake");
74
74
  var snakeCase = { table: createTableFactory("snake") };
75
75
  var camelCase = { table: createTableFactory("camel") };
76
+ var noCasing = { table: createTableFactory("none") };
76
77
  // ../bungres-orm/src/schema/columns.ts
77
78
  function buildColumn(dataType, nameOrOpts, opts) {
78
79
  let name = "";
@@ -167,14 +168,14 @@ function sqlJoin(chunks, separator = ", ") {
167
168
  function rawSql(query) {
168
169
  return { sql: query, params: [] };
169
170
  }
170
- // ../bungres-orm/src/core/conditions.ts
171
- var colName = (c) => {
171
+ function colName(c) {
172
172
  if (typeof c === "string")
173
173
  return `"${c}"`;
174
174
  if (isSQLChunk(c))
175
175
  return c.sql;
176
176
  return c.tableName ? `${c.tableName}."${c.name}"` : `"${c.name}"`;
177
- };
177
+ }
178
+ // ../bungres-orm/src/core/conditions.ts
178
179
  function isColumnConfig(val) {
179
180
  return val !== null && typeof val === "object" && "name" in val && "dataType" in val;
180
181
  }
@@ -231,6 +232,14 @@ var inArray = (column, values) => {
231
232
  const placeholders = params.map((_, i) => `$${i + 1}`).join(", ");
232
233
  return { sql: `${colName(column)} = ANY(ARRAY[${placeholders}])`, params };
233
234
  };
235
+ var notInArray = (column, values) => {
236
+ if (values.length === 0)
237
+ return rawSql("TRUE");
238
+ const params = values;
239
+ const placeholders = params.map((_, i) => `$${i + 1}`).join(", ");
240
+ return { sql: `${colName(column)} != ALL(ARRAY[${placeholders}])`, params };
241
+ };
242
+ var between = (column, min, max) => sql`${rawSql(colName(column))} BETWEEN ${min} AND ${max}`;
234
243
  var and = (...conditions) => sqlJoin(conditions, " AND ");
235
244
  var or = (...conditions) => {
236
245
  const joined = sqlJoin(conditions, " OR ");
@@ -281,6 +290,10 @@ function parseWhereObject(tableConfig, whereObj) {
281
290
  conditions.push(lte(columnArg, opVal.lte));
282
291
  if (opVal.in !== undefined)
283
292
  conditions.push(inArray(columnArg, opVal.in));
293
+ if (opVal.notIn !== undefined)
294
+ conditions.push(notInArray(columnArg, opVal.notIn));
295
+ if (opVal.between !== undefined)
296
+ conditions.push(between(columnArg, opVal.between[0], opVal.between[1]));
284
297
  if (opVal.like !== undefined)
285
298
  conditions.push(like(columnArg, opVal.like));
286
299
  if (opVal.ilike !== undefined)
@@ -361,6 +374,9 @@ class DeleteBuilder {
361
374
  if (this._returning) {
362
375
  query += " RETURNING " + (this._returning[0] === "*" ? Object.keys(getTableConfig(this._table).columns).map((c) => `"${getTableConfig(this._table).columns[c].name}" AS "${c}"`).join(", ") : this._returning.map((c) => `"${getTableConfig(this._table).columns[c]?.name ?? c}" AS "${c}"`).join(", "));
363
376
  }
377
+ if (this._comment) {
378
+ query += ` /* ${this._comment.replace(/\*\//g, "")} */`;
379
+ }
364
380
  return { sql: query, params };
365
381
  }
366
382
  }
@@ -471,7 +487,7 @@ class InsertBuilder {
471
487
  query += " RETURNING " + (this._returning[0] === "*" ? Object.keys(tConfig.columns).map((c) => `"${tConfig.columns[c].name}" AS "${c}"`).join(", ") : this._returning.map((c) => `"${tConfig.columns[c]?.name ?? c}" AS "${c}"`).join(", "));
472
488
  }
473
489
  if (this._comment) {
474
- query += ` /* ${this._comment} */`;
490
+ query += ` /* ${this._comment.replace(/\*\//g, "")} */`;
475
491
  }
476
492
  return { sql: query, params };
477
493
  }
@@ -668,7 +684,7 @@ class SelectBuilder {
668
684
  query += ` OFFSET $${params.length}`;
669
685
  }
670
686
  if (this._comment) {
671
- query += ` /* ${this._comment} */`;
687
+ query += ` /* ${this._comment.replace(/\*\//g, "")} */`;
672
688
  }
673
689
  return { sql: query, params };
674
690
  }
@@ -768,10 +784,22 @@ class UpdateBuilder {
768
784
  if (this._returning) {
769
785
  query += " RETURNING " + (this._returning[0] === "*" ? Object.keys(getTableConfig(this._table).columns).map((c) => `"${getTableConfig(this._table).columns[c].name}" AS "${c}"`).join(", ") : this._returning.map((c) => `"${getTableConfig(this._table).columns[c]?.name ?? c}" AS "${c}"`).join(", "));
770
786
  }
787
+ if (this._comment) {
788
+ query += ` /* ${this._comment.replace(/\*\//g, "")} */`;
789
+ }
771
790
  return { sql: query, params };
772
791
  }
773
792
  }
774
793
  // ../bungres-orm/src/builders/relational.ts
794
+ function getPkColumn(tableConfig) {
795
+ if (tableConfig.primaryKeys?.length > 0)
796
+ return tableConfig.primaryKeys[0];
797
+ for (const [, col2] of Object.entries(tableConfig.columns)) {
798
+ if (col2.primaryKey)
799
+ return col2.name;
800
+ }
801
+ return "id";
802
+ }
775
803
  var _relationsCache = new WeakMap;
776
804
 
777
805
  class RelationalQueryBuilder {
@@ -922,14 +950,17 @@ class RelationalQueryBuilder {
922
950
  if (relations.ones[relKey]) {
923
951
  const rel = relations.ones[relKey];
924
952
  const subAlias = `${alias}_${relKey}`;
925
- const joinCond = `"${subAlias}"."${this._schema[rel.targetTable][TableConfigSymbol].columns.id?.name ?? "id"}" = "${alias}"."${rel.sourceColumn}"`;
953
+ const targetConfig = this._schema[rel.targetTable][TableConfigSymbol];
954
+ const targetPk = getPkColumn(targetConfig);
955
+ const joinCond = `"${subAlias}"."${targetPk}" = "${alias}"."${rel.sourceColumn}"`;
926
956
  const subQuery = this._buildSelectJson(rel.targetTable, rArgs, subAlias, params, alias, joinCond);
927
957
  const subSql = `SELECT ${subQuery.sql} ${subQuery.from}`;
928
958
  jsonFields.push(`'${relKey}', (${subSql})`);
929
959
  } else if (relations.manys[relKey]) {
930
960
  const rel = relations.manys[relKey];
931
961
  const subAlias = `${alias}_${relKey}`;
932
- const joinCond = `"${subAlias}"."${rel.targetColumn}" = "${alias}"."${tableConfig.columns.id?.name ?? "id"}"`;
962
+ const thisPk = getPkColumn(tableConfig);
963
+ const joinCond = `"${subAlias}"."${rel.targetColumn}" = "${alias}"."${thisPk}"`;
933
964
  const subQuery = this._buildSelectJson(rel.targetTable, rArgs, subAlias, params, alias, joinCond);
934
965
  const aggAlias = `${subAlias}_agg`;
935
966
  const aggSql = `
@@ -948,9 +979,9 @@ class RelationalQueryBuilder {
948
979
  const junctionTableConfig = this._schema[rel.junctionTable][TableConfigSymbol];
949
980
  const fromExtra = `
950
981
  INNER JOIN ${junctionTableConfig.qualifiedName} AS "${junctionAlias}"
951
- ON "${junctionAlias}"."${rel.joinTargetColumn}" = "${subAlias}"."${targetTableConfig.columns.id?.name ?? "id"}"
982
+ ON "${junctionAlias}"."${rel.joinTargetColumn}" = "${subAlias}"."${getPkColumn(targetTableConfig)}"
952
983
  `;
953
- const whereExtra = `"${junctionAlias}"."${rel.joinSourceColumn}" = "${alias}"."${tableConfig.columns.id?.name ?? "id"}"`;
984
+ const whereExtra = `"${junctionAlias}"."${rel.joinSourceColumn}" = "${alias}"."${getPkColumn(tableConfig)}"`;
954
985
  const subQuery = this._buildSelectJson(rel.targetTable, rArgs, subAlias, params, alias, whereExtra, fromExtra);
955
986
  const aggSql = `
956
987
  SELECT COALESCE(json_agg(_sub.obj), '[]'::json)
@@ -1162,7 +1193,7 @@ class BungresTransaction {
1162
1193
  return Array.from(result2);
1163
1194
  }
1164
1195
  }
1165
- function createDB(config2) {
1196
+ function bungres(config2) {
1166
1197
  const db2 = new BungresDB(config2);
1167
1198
  if (typeof config2 === "object" && config2.schema) {
1168
1199
  const schema = config2.schema;
@@ -1331,9 +1362,9 @@ function isTable(value) {
1331
1362
  }
1332
1363
 
1333
1364
  // src/utils/colors.ts
1334
- function colorize(text2, color) {
1365
+ function colorize(text, color) {
1335
1366
  const code2 = Bun.color(color, "ansi");
1336
- return code2 ? `${code2}${text2}\x1B[0m` : text2;
1367
+ return code2 ? `${code2}${text}\x1B[0m` : text;
1337
1368
  }
1338
1369
 
1339
1370
  // src/commands/drop.ts
@@ -1345,14 +1376,19 @@ async function runDrop(config2, opts = {}) {
1345
1376
  }
1346
1377
  const sql2 = new Bun.SQL(config2.dbUrl);
1347
1378
  try {
1348
- const existingTablesResult = await sql2`
1349
- SELECT table_name
1350
- FROM information_schema.tables
1351
- WHERE table_schema = 'public' OR table_schema = current_schema()
1352
- `;
1353
- const existingTableNames = new Set(existingTablesResult.map((row) => row.table_name));
1354
- const migrationTableExists = existingTableNames.has("__bungres_migrations");
1355
- const pushTableExists = existingTableNames.has("__bungres_push");
1379
+ const userSchema = config2.dbSchema;
1380
+ const existingUserTablesResult = await sql2.unsafe(`SELECT table_name FROM information_schema.tables WHERE table_schema = $1`, [userSchema]);
1381
+ const existingTableNames = new Set(existingUserTablesResult.map((r) => r.table_name));
1382
+ const migTableCheck = await sql2.unsafe(`SELECT EXISTS (
1383
+ SELECT 1 FROM information_schema.tables
1384
+ WHERE table_schema = $1 AND table_name = $2
1385
+ ) AS exists`, [config2.migrationsSchema, config2.migrationsTable]);
1386
+ const migrationTableExists = migTableCheck[0]?.exists ?? false;
1387
+ const pushTableCheck = await sql2.unsafe(`SELECT EXISTS (
1388
+ SELECT 1 FROM information_schema.tables
1389
+ WHERE table_schema = $1 AND table_name = $2
1390
+ ) AS exists`, [config2.migrationsSchema, "__bungres_push"]);
1391
+ const pushTableExists = pushTableCheck[0]?.exists ?? false;
1356
1392
  const tablesToDrop = schemas2.filter((s) => existingTableNames.has(s.config.name));
1357
1393
  if (tablesToDrop.length === 0 && !migrationTableExists && !pushTableExists) {
1358
1394
  console.log("No tables to drop (they either don't exist or were already dropped).");
@@ -1360,10 +1396,10 @@ async function runDrop(config2, opts = {}) {
1360
1396
  }
1361
1397
  const tableNamesToPrint = tablesToDrop.map((s) => (s.config.schema ? s.config.schema + "." : "") + s.config.name);
1362
1398
  if (migrationTableExists) {
1363
- tableNamesToPrint.push("__bungres_migrations");
1399
+ tableNamesToPrint.push(`${config2.migrationsSchema}.${config2.migrationsTable}`);
1364
1400
  }
1365
1401
  if (pushTableExists) {
1366
- tableNamesToPrint.push("__bungres_push");
1402
+ tableNamesToPrint.push(`${config2.migrationsSchema}.__bungres_push`);
1367
1403
  }
1368
1404
  if (!opts.force) {
1369
1405
  console.warn(colorize(`
@@ -1387,12 +1423,13 @@ Are you sure? Type YES to continue: `, "cyan"));
1387
1423
  console.log(colorize(` \u2713 dropped ${entry.config.name}`, "green"));
1388
1424
  }
1389
1425
  if (migrationTableExists) {
1390
- await sql2.unsafe("DROP TABLE IF EXISTS public.__bungres_migrations CASCADE");
1391
- console.log(colorize(` \u2713 dropped __bungres_migrations`, "green"));
1426
+ const qualifiedMigTable = `"${config2.migrationsSchema}"."${config2.migrationsTable}"`;
1427
+ await sql2.unsafe(`DROP TABLE IF EXISTS ${qualifiedMigTable} CASCADE`);
1428
+ console.log(colorize(` \u2713 dropped ${config2.migrationsSchema}.${config2.migrationsTable}`, "green"));
1392
1429
  }
1393
1430
  if (pushTableExists) {
1394
- await sql2.unsafe("DROP TABLE IF EXISTS public.__bungres_push CASCADE");
1395
- console.log(colorize(` \u2713 dropped __bungres_push`, "green"));
1431
+ await sql2.unsafe(`DROP TABLE IF EXISTS "${config2.migrationsSchema}"."__bungres_push" CASCADE`);
1432
+ console.log(colorize(` \u2713 dropped ${config2.migrationsSchema}.__bungres_push`, "green"));
1396
1433
  }
1397
1434
  console.log(colorize(`
1398
1435
  Drop complete.`, "green"));
@@ -1403,19 +1440,22 @@ Drop complete.`, "green"));
1403
1440
 
1404
1441
  // src/commands/migrate.ts
1405
1442
  import { join as join2, resolve as resolve2 } from "path";
1406
- var MIGRATIONS_TABLE = "__bungres_migrations";
1407
- var CREATE_MIGRATIONS_TABLE = `
1408
- CREATE TABLE IF NOT EXISTS "${MIGRATIONS_TABLE}" (
1443
+ async function runMigrate(config2) {
1444
+ const migrationsDir = resolve2(config2.out);
1445
+ const sql2 = new Bun.SQL(config2.dbUrl);
1446
+ const table2 = config2.migrationsTable;
1447
+ const schema = config2.migrationsSchema;
1448
+ const qualifiedTable = `"${schema}"."${table2}"`;
1449
+ const createSchema = `CREATE SCHEMA IF NOT EXISTS "${schema}";`;
1450
+ const createMigrationsTable = `
1451
+ CREATE TABLE IF NOT EXISTS ${qualifiedTable} (
1409
1452
  id SERIAL PRIMARY KEY,
1410
1453
  name TEXT NOT NULL UNIQUE,
1411
1454
  applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
1412
- );
1413
- `.trim();
1414
- async function runMigrate(config2) {
1415
- const migrationsDir = resolve2(config2.migrationsDir);
1416
- const sql2 = new Bun.SQL(config2.dbUrl);
1455
+ );`.trim();
1417
1456
  try {
1418
- await sql2.unsafe(CREATE_MIGRATIONS_TABLE);
1457
+ await sql2.unsafe(createSchema);
1458
+ await sql2.unsafe(createMigrationsTable);
1419
1459
  const glob = new Bun.Glob("*.sql");
1420
1460
  const files = [];
1421
1461
  for await (const file of glob.scan({ cwd: migrationsDir, absolute: false })) {
@@ -1427,7 +1467,7 @@ async function runMigrate(config2) {
1427
1467
  console.log(colorize("Run `bungres generate` first.", "yellow"));
1428
1468
  return;
1429
1469
  }
1430
- const applied = await sql2.unsafe(`SELECT name FROM "${MIGRATIONS_TABLE}"`);
1470
+ const applied = await sql2.unsafe(`SELECT name FROM ${qualifiedTable}`);
1431
1471
  const appliedSet = new Set(applied.map((r) => r.name));
1432
1472
  const pending = files.filter((f) => !appliedSet.has(f));
1433
1473
  if (pending.length === 0) {
@@ -1445,11 +1485,11 @@ ${content}
1445
1485
  `);
1446
1486
  }
1447
1487
  await sql2.transaction(async (txSql) => {
1448
- const statements = content.split(";").map((s) => s.trim()).filter(Boolean);
1488
+ const statements = config2.breakpoints ? content.split(/-->statement-breakpoint/g).flatMap((chunk) => chunk.split(";").map((s) => s.trim()).filter(Boolean)) : content.split(";").map((s) => s.trim()).filter(Boolean);
1449
1489
  for (const stmt of statements) {
1450
1490
  await txSql.unsafe(stmt + ";");
1451
1491
  }
1452
- await txSql.unsafe(`INSERT INTO "${MIGRATIONS_TABLE}" (name) VALUES ($1)`, [file]);
1492
+ await txSql.unsafe(`INSERT INTO ${qualifiedTable} (name) VALUES ($1)`, [file]);
1453
1493
  });
1454
1494
  console.log(colorize(` \u2713 ${file}`, "green"));
1455
1495
  }
@@ -1605,6 +1645,14 @@ function diffColumn(prev, next) {
1605
1645
  const col2 = `"${next.name}"`;
1606
1646
  if (prev.dataType !== next.dataType) {
1607
1647
  changes.push(`ALTER COLUMN ${col2} TYPE ${next.dataType.toUpperCase()} USING ${col2}::${next.dataType}`);
1648
+ } else {
1649
+ const prevLen = prev.length;
1650
+ const nextLen = next.length;
1651
+ if (prevLen !== nextLen && (next.dataType === "varchar" || next.dataType === "char")) {
1652
+ const typeName = next.dataType === "varchar" ? "VARCHAR" : "CHAR";
1653
+ const typeStr = nextLen ? `${typeName}(${nextLen})` : typeName;
1654
+ changes.push(`ALTER COLUMN ${col2} TYPE ${typeStr}`);
1655
+ }
1608
1656
  }
1609
1657
  if (!prev.notNull && next.notNull) {
1610
1658
  changes.push(`ALTER COLUMN ${col2} SET NOT NULL`);
@@ -1648,7 +1696,7 @@ async function runGenerate(config2, name) {
1648
1696
  console.warn(colorize("No table definitions found. Check your schema glob pattern.", "yellow"));
1649
1697
  return;
1650
1698
  }
1651
- const migrationsDir = resolve3(config2.migrationsDir);
1699
+ const migrationsDir = resolve3(config2.out);
1652
1700
  await Bun.$`mkdir -p ${migrationsDir}`.quiet();
1653
1701
  const currentSnapshot = Object.fromEntries(schemas2.map((s) => [s.config.name, s.config]));
1654
1702
  const snapshotPath = join3(migrationsDir, SNAPSHOT_FILE);
@@ -1671,8 +1719,6 @@ async function runGenerate(config2, name) {
1671
1719
  if (isFirstMigration) {
1672
1720
  const sorted = topoSort(schemas2);
1673
1721
  statements = [
1674
- `CREATE EXTENSION IF NOT EXISTS "pgcrypto";`,
1675
- ``,
1676
1722
  ...sorted.flatMap((entry) => [
1677
1723
  `-- ${entry.exportName}`,
1678
1724
  generateCreateTable(entry.config, true),
@@ -1764,7 +1810,7 @@ export default defineConfig({
1764
1810
  schema: "./src/db/schema.ts",
1765
1811
  out: "./bungres",
1766
1812
  dbCredentials: {
1767
- url: Bun.env.DATABASE_URL!,
1813
+ url: process.env.DATABASE_URL!,
1768
1814
  },
1769
1815
  });
1770
1816
  `;
@@ -1779,12 +1825,12 @@ export default defineConfig({
1779
1825
  uuid,
1780
1826
  varchar,
1781
1827
  timestamptz,
1782
- snakeCase,
1828
+ table,
1783
1829
  unique,
1784
1830
  index
1785
1831
  } from "@bungres/orm";
1786
1832
 
1787
- export const users = snakeCase.table("users", {
1833
+ export const users = table("users", {
1788
1834
  id: uuid({ primaryKey: true }),
1789
1835
  name: varchar({ length: 255, notNull: true }),
1790
1836
  email: varchar({ length: 255, notNull: true }),
@@ -1800,12 +1846,12 @@ export const users = snakeCase.table("users", {
1800
1846
  } catch (e) {
1801
1847
  console.error("Failed to create schema file:", e);
1802
1848
  }
1803
- const clientContent = `import { createDB } from "@bungres/orm";
1849
+ const clientContent = `import { bungres } from "@bungres/orm";
1804
1850
  import * as schema from "./schema";
1805
1851
 
1806
1852
  const url = Bun.env.DATABASE_URL || "postgres://postgres:postgres@localhost:5432/postgres";
1807
1853
 
1808
- export const db = createDB({ url, schema });
1854
+ export const db = bungres({ url, schema });
1809
1855
  `;
1810
1856
  try {
1811
1857
  await Bun.write(join4(dbDir, "client.ts"), clientContent);
@@ -1921,7 +1967,7 @@ function generateSchemaTS(tableMap, dbSchema) {
1921
1967
  `// Generated at: ${new Date().toISOString()}`,
1922
1968
  ``,
1923
1969
  `import {`,
1924
- ` snakeCase,`,
1970
+ ` table,`,
1925
1971
  ` text, varchar, char, integer, bigint, smallint,`,
1926
1972
  ` serial, bigserial, boolean, real, doublePrecision,`,
1927
1973
  ` numeric, decimal, json, jsonb,`,
@@ -1933,7 +1979,7 @@ function generateSchemaTS(tableMap, dbSchema) {
1933
1979
  ];
1934
1980
  for (const [, table2] of tableMap) {
1935
1981
  const varName = toCamelCase(table2.tableName);
1936
- lines.push(`export const ${varName} = snakeCase.table("${table2.tableName}", {`);
1982
+ lines.push(`export const ${varName} = table("${table2.tableName}", {`);
1937
1983
  for (const col2 of table2.columns) {
1938
1984
  const colExpr = buildColumnExpression(col2);
1939
1985
  lines.push(` ${toCamelCase(col2.name)}: ${colExpr},`);
@@ -2090,13 +2136,15 @@ async function runPush(config2, opts = {}) {
2090
2136
  }
2091
2137
  const sql2 = new Bun.SQL(config2.dbUrl);
2092
2138
  try {
2139
+ await sql2.unsafe(`CREATE SCHEMA IF NOT EXISTS "${config2.migrationsSchema}";`);
2140
+ const qualifiedPush = `"${config2.migrationsSchema}"."__bungres_push"`;
2093
2141
  await sql2.unsafe(`
2094
- CREATE TABLE IF NOT EXISTS public.__bungres_push (
2142
+ CREATE TABLE IF NOT EXISTS ${qualifiedPush} (
2095
2143
  id SERIAL PRIMARY KEY,
2096
2144
  snapshot JSONB NOT NULL
2097
2145
  );
2098
2146
  `);
2099
- const rows = await sql2.unsafe(`SELECT snapshot FROM public.__bungres_push ORDER BY id DESC LIMIT 1;`);
2147
+ const rows = await sql2.unsafe(`SELECT snapshot FROM ${qualifiedPush} ORDER BY id DESC LIMIT 1;`);
2100
2148
  let prevSnapshot = {};
2101
2149
  if (rows.length > 0) {
2102
2150
  prevSnapshot = typeof rows[0].snapshot === "string" ? JSON.parse(rows[0].snapshot) : rows[0].snapshot;
@@ -2131,14 +2179,13 @@ Are you sure you want to push these changes? Type YES to continue: `);
2131
2179
  }
2132
2180
  console.log(`
2133
2181
  Pushing changes...`);
2134
- await sql2.unsafe(`CREATE EXTENSION IF NOT EXISTS "pgcrypto";`);
2135
2182
  for (const stmt of diff.statements) {
2136
2183
  if (config2.verbose) {
2137
2184
  console.log(`-- ${stmt}`);
2138
2185
  }
2139
2186
  await sql2.unsafe(stmt);
2140
2187
  }
2141
- await sql2.unsafe(`INSERT INTO public.__bungres_push (snapshot) VALUES ($1);`, [JSON.stringify(currentSnapshot)]);
2188
+ await sql2.unsafe(`INSERT INTO ${qualifiedPush} (snapshot) VALUES ($1);`, [JSON.stringify(currentSnapshot)]);
2142
2189
  console.log(`
2143
2190
  Push complete.`);
2144
2191
  } finally {
@@ -2193,7 +2240,7 @@ Running seeder: ${config2.seed}...`, "cyan"));
2193
2240
  cwd: process.cwd(),
2194
2241
  stdout: "inherit",
2195
2242
  stderr: "inherit",
2196
- env: { ...process.env, DATABASE_URL: config2.dbUrl }
2243
+ env: { ...Bun.env, DATABASE_URL: config2.dbUrl }
2197
2244
  });
2198
2245
  const exitCode = await proc.exited;
2199
2246
  if (exitCode === 0) {
@@ -2208,15 +2255,17 @@ Seed failed with exit code ${exitCode}.`);
2208
2255
 
2209
2256
  // src/commands/status.ts
2210
2257
  import { resolve as resolve6 } from "path";
2211
- var MIGRATIONS_TABLE2 = "__bungres_migrations";
2212
2258
  async function runStatus(config2) {
2213
- const migrationsDir = resolve6(config2.migrationsDir);
2259
+ const migrationsDir = resolve6(config2.out);
2214
2260
  const sql2 = new Bun.SQL(config2.dbUrl);
2261
+ const table2 = config2.migrationsTable;
2262
+ const schema = config2.migrationsSchema;
2263
+ const qualifiedTable = `"${schema}"."${table2}"`;
2215
2264
  try {
2216
2265
  const tableCheck = await sql2.unsafe(`SELECT EXISTS (
2217
2266
  SELECT 1 FROM information_schema.tables
2218
- WHERE table_name = $1
2219
- ) AS exists`, [MIGRATIONS_TABLE2]);
2267
+ WHERE table_schema = $1 AND table_name = $2
2268
+ ) AS exists`, [schema, table2]);
2220
2269
  const trackingExists = tableCheck[0]?.exists ?? false;
2221
2270
  const glob = new Bun.Glob("*.sql");
2222
2271
  const files = [];
@@ -2230,7 +2279,7 @@ async function runStatus(config2) {
2230
2279
  }
2231
2280
  let appliedSet = new Set;
2232
2281
  if (trackingExists) {
2233
- const applied = await sql2.unsafe(`SELECT name FROM "${MIGRATIONS_TABLE2}" ORDER BY applied_at`);
2282
+ const applied = await sql2.unsafe(`SELECT name FROM ${qualifiedTable} ORDER BY applied_at`);
2234
2283
  appliedSet = new Set(applied.map((r) => r.name));
2235
2284
  }
2236
2285
  console.log(colorize(`
@@ -2263,22 +2312,39 @@ async function runStudio(config2) {
2263
2312
  for (const s of schemas2) {
2264
2313
  schemaObj2[s.exportName] = s.table;
2265
2314
  }
2266
- const db2 = createDB({ url: config2.dbUrl, schema: schemaObj2 });
2267
- const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 5555;
2315
+ const db2 = bungres({ url: config2.dbUrl, schema: schemaObj2 });
2316
+ const port = Bun.env.PORT ? parseInt(Bun.env.PORT, 10) : 5555;
2268
2317
  const server = Bun.serve({
2269
2318
  port,
2270
2319
  async fetch(req) {
2271
2320
  const url = new URL(req.url);
2272
2321
  if (req.method === "GET" && url.pathname === "/api/tables") {
2273
- const tables = schemas2.map((s) => ({
2274
- name: s.config.name,
2275
- exportName: s.exportName,
2276
- columns: Object.entries(s.config.columns).map(([key, col2]) => ({
2277
- name: col2.name,
2278
- type: col2.dataType,
2279
- primaryKey: col2.primaryKey
2280
- }))
2281
- }));
2322
+ const tables = schemas2.map((s) => {
2323
+ const fkColumns = new Set;
2324
+ if (s.config.foreignKeys) {
2325
+ s.config.foreignKeys.forEach((fk) => {
2326
+ fk.columns.forEach((col2) => fkColumns.add(col2));
2327
+ });
2328
+ }
2329
+ const columns2 = Object.entries(s.config.columns).map(([key, col2]) => {
2330
+ const fk = col2.references ? {
2331
+ table: col2.references.table,
2332
+ column: col2.references.column
2333
+ } : fkColumns.has(col2.name) ? { isForeignKey: true } : null;
2334
+ return {
2335
+ name: col2.name,
2336
+ type: col2.dataType,
2337
+ primaryKey: col2.primaryKey,
2338
+ foreignKey: fk
2339
+ };
2340
+ });
2341
+ return {
2342
+ name: s.config.name,
2343
+ exportName: s.exportName,
2344
+ columns: columns2,
2345
+ foreignKeys: s.config.foreignKeys || []
2346
+ };
2347
+ });
2282
2348
  return new Response(JSON.stringify(tables), {
2283
2349
  headers: { "Content-Type": "application/json" }
2284
2350
  });
@@ -2356,15 +2422,16 @@ var htmlTemplate = `
2356
2422
  <style>
2357
2423
  :root {
2358
2424
  --bg-color: #09090b;
2359
- --panel-bg: rgba(24, 24, 27, 0.7);
2425
+ --panel-bg: #18181b;
2360
2426
  --border-color: #27272a;
2361
- --text-main: #f4f4f5;
2427
+ --text-main: #fafafa;
2362
2428
  --text-muted: #a1a1aa;
2363
2429
  --accent-color: #8b5cf6;
2364
2430
  --accent-hover: #7c3aed;
2365
- --header-bg: rgba(9, 9, 11, 0.85);
2366
- --row-hover: rgba(39, 39, 42, 0.8);
2367
- --glass-border: rgba(255, 255, 255, 0.08);
2431
+ --header-bg: #09090b;
2432
+ --row-hover: #27272a;
2433
+ --input-bg: #18181b;
2434
+ --muted-bg: #27272a;
2368
2435
 
2369
2436
  --type-number: #f472b6;
2370
2437
  --type-string: #60a5fa;
@@ -2389,45 +2456,39 @@ var htmlTemplate = `
2389
2456
 
2390
2457
  /* Sidebar */
2391
2458
  .sidebar {
2392
- width: 280px;
2459
+ width: 240px;
2393
2460
  background-color: var(--panel-bg);
2394
- backdrop-filter: blur(12px);
2395
- -webkit-backdrop-filter: blur(12px);
2396
2461
  border-right: 1px solid var(--border-color);
2397
2462
  display: flex;
2398
2463
  flex-direction: column;
2399
- box-shadow: 4px 0 24px rgba(0,0,0,0.2);
2400
2464
  z-index: 20;
2401
2465
  }
2402
2466
 
2403
2467
  .sidebar-header {
2404
- padding: 24px;
2405
- border-bottom: 1px solid var(--glass-border);
2468
+ padding: 16px;
2469
+ border-bottom: 1px solid var(--border-color);
2406
2470
  display: flex;
2407
2471
  align-items: center;
2408
- gap: 12px;
2409
- background: linear-gradient(to bottom, rgba(255,255,255,0.03), transparent);
2472
+ gap: 8px;
2473
+ background-color: var(--panel-bg);
2410
2474
  }
2411
2475
 
2412
2476
  .sidebar-header h1 {
2413
2477
  margin: 0;
2414
- font-size: 18px;
2478
+ font-size: 14px;
2415
2479
  font-weight: 600;
2416
- background: linear-gradient(to right, #fff, #a1a1aa);
2417
- -webkit-background-clip: text;
2418
- -webkit-text-fill-color: transparent;
2419
- letter-spacing: -0.5px;
2480
+ color: var(--text-main);
2481
+ letter-spacing: -0.25px;
2420
2482
  }
2421
2483
 
2422
2484
  .sidebar-header svg {
2423
2485
  color: var(--accent-color);
2424
- filter: drop-shadow(0 0 8px rgba(139, 92, 246, 0.5));
2425
2486
  }
2426
2487
 
2427
2488
  .table-list {
2428
2489
  flex: 1;
2429
2490
  overflow-y: auto;
2430
- padding: 16px 12px;
2491
+ padding: 8px;
2431
2492
  list-style: none;
2432
2493
  margin: 0;
2433
2494
  }
@@ -2441,39 +2502,36 @@ var htmlTemplate = `
2441
2502
  }
2442
2503
 
2443
2504
  .table-item {
2444
- padding: 12px 16px;
2505
+ padding: 8px 12px;
2445
2506
  cursor: pointer;
2446
- font-size: 15px;
2507
+ font-size: 13px;
2447
2508
  font-weight: 500;
2448
2509
  color: var(--text-muted);
2449
- border-radius: 8px;
2450
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
2510
+ border-radius: 6px;
2511
+ transition: all 0.15s ease;
2451
2512
  display: flex;
2452
2513
  align-items: center;
2453
- gap: 10px;
2454
- margin-bottom: 4px;
2514
+ gap: 8px;
2515
+ margin-bottom: 2px;
2455
2516
  border: 1px solid transparent;
2456
2517
  }
2457
2518
 
2458
2519
  .table-item:hover {
2459
- background-color: rgba(255, 255, 255, 0.05);
2520
+ background-color: var(--muted-bg);
2460
2521
  color: var(--text-main);
2461
- transform: translateX(4px);
2462
2522
  }
2463
2523
 
2464
2524
  .table-item.active {
2465
- background-color: rgba(139, 92, 246, 0.15);
2525
+ background-color: var(--accent-color);
2466
2526
  color: #fff;
2467
- border: 1px solid rgba(139, 92, 246, 0.3);
2468
- box-shadow: 0 4px 12px rgba(139, 92, 246, 0.1);
2527
+ border-color: var(--accent-color);
2469
2528
  }
2470
2529
 
2471
2530
  .table-item svg {
2472
- transition: transform 0.3s ease;
2531
+ flex-shrink: 0;
2473
2532
  }
2474
2533
  .table-item.active svg {
2475
- color: var(--accent-color);
2476
- transform: scale(1.1);
2534
+ color: #fff;
2477
2535
  }
2478
2536
 
2479
2537
  /* Main Content */
@@ -2485,31 +2543,29 @@ var htmlTemplate = `
2485
2543
  }
2486
2544
 
2487
2545
  .main-header {
2488
- height: 70px;
2489
- padding: 0 24px;
2546
+ height: 56px;
2547
+ padding: 0 16px;
2490
2548
  border-bottom: 1px solid var(--border-color);
2491
2549
  display: flex;
2492
2550
  align-items: center;
2493
2551
  justify-content: space-between;
2494
2552
  background-color: var(--header-bg);
2495
- backdrop-filter: blur(12px);
2496
- -webkit-backdrop-filter: blur(12px);
2497
2553
  z-index: 10;
2498
2554
  }
2499
2555
 
2500
2556
  .main-header h2 {
2501
2557
  margin: 0;
2502
- font-size: 20px;
2558
+ font-size: 14px;
2503
2559
  font-weight: 600;
2504
- color: white;
2505
- letter-spacing: -0.5px;
2560
+ color: var(--text-main);
2561
+ letter-spacing: -0.25px;
2506
2562
  display: flex;
2507
2563
  align-items: center;
2508
- gap: 10px;
2564
+ gap: 8px;
2509
2565
  }
2510
2566
 
2511
2567
  .btn {
2512
- background-color: rgba(255, 255, 255, 0.05);
2568
+ background-color: var(--input-bg);
2513
2569
  border: 1px solid var(--border-color);
2514
2570
  color: var(--text-main);
2515
2571
  padding: 6px 12px;
@@ -2518,14 +2574,14 @@ var htmlTemplate = `
2518
2574
  font-weight: 500;
2519
2575
  font-family: 'Outfit', sans-serif;
2520
2576
  cursor: pointer;
2521
- transition: all 0.2s ease;
2577
+ transition: all 0.15s ease;
2522
2578
  display: flex;
2523
2579
  align-items: center;
2524
- gap: 8px;
2580
+ gap: 6px;
2525
2581
  }
2526
2582
 
2527
2583
  .btn:hover:not(:disabled) {
2528
- background-color: rgba(255, 255, 255, 0.1);
2584
+ background-color: var(--muted-bg);
2529
2585
  }
2530
2586
 
2531
2587
  .btn:active:not(:disabled) {
@@ -2533,7 +2589,7 @@ var htmlTemplate = `
2533
2589
  }
2534
2590
 
2535
2591
  .btn:disabled {
2536
- opacity: 0.4;
2592
+ opacity: 0.5;
2537
2593
  cursor: not-allowed;
2538
2594
  }
2539
2595
 
@@ -2548,14 +2604,20 @@ var htmlTemplate = `
2548
2604
 
2549
2605
  /* Data Table */
2550
2606
  .data-grid-container {
2551
- background-color: rgba(24, 24, 27, 0.5);
2607
+ background-color: var(--bg-color);
2552
2608
  border: 1px solid var(--border-color);
2553
2609
  border-radius: 0;
2554
- overflow: auto;
2555
- box-shadow: 0 8px 32px rgba(0,0,0,0.2);
2556
- backdrop-filter: blur(8px);
2557
- max-height: 100%;
2610
+ overflow: hidden;
2611
+ height: 100%;
2558
2612
  width: 100%;
2613
+ display: flex;
2614
+ flex-direction: column;
2615
+ }
2616
+
2617
+ .data-grid-container > .table-wrapper {
2618
+ flex: 1;
2619
+ overflow: auto;
2620
+ min-height: 0;
2559
2621
  }
2560
2622
 
2561
2623
  .data-grid-container::-webkit-scrollbar {
@@ -2570,6 +2632,7 @@ var htmlTemplate = `
2570
2632
  border-radius: 4px;
2571
2633
  }
2572
2634
 
2635
+
2573
2636
  table {
2574
2637
  width: auto;
2575
2638
  border-collapse: separate;
@@ -2580,11 +2643,11 @@ var htmlTemplate = `
2580
2643
  }
2581
2644
 
2582
2645
  th {
2583
- background-color: rgba(24, 24, 27, 0.95);
2584
- backdrop-filter: blur(4px);
2646
+ background-color: var(--panel-bg);
2585
2647
  color: var(--text-main);
2586
2648
  font-weight: 500;
2587
- padding: 12px 20px;
2649
+ font-size: 12px;
2650
+ padding: 10px 16px;
2588
2651
  border-bottom: 1px solid var(--border-color);
2589
2652
  border-right: 1px solid var(--border-color);
2590
2653
  white-space: nowrap;
@@ -2592,24 +2655,16 @@ var htmlTemplate = `
2592
2655
  top: 0;
2593
2656
  z-index: 10;
2594
2657
  }
2595
-
2596
-
2597
- th::after {
2598
- content: '';
2599
- position: absolute;
2600
- bottom: -1px; left: 0; right: 0;
2601
- height: 1px;
2602
- background: var(--border-color);
2603
- }
2604
2658
 
2605
2659
  .col-header {
2606
2660
  display: flex;
2607
2661
  flex-direction: column;
2608
- gap: 4px;
2662
+ gap: 2px;
2609
2663
  }
2610
2664
 
2611
2665
  .col-name {
2612
2666
  font-weight: 600;
2667
+ font-size: 13px;
2613
2668
  display: flex;
2614
2669
  align-items: center;
2615
2670
  gap: 6px;
@@ -2617,22 +2672,35 @@ var htmlTemplate = `
2617
2672
 
2618
2673
  .pk-badge {
2619
2674
  font-size: 10px;
2620
- background: rgba(251, 191, 36, 0.2);
2675
+ background: rgba(251, 191, 36, 0.15);
2621
2676
  color: #fbbf24;
2622
- padding: 2px 6px;
2677
+ padding: 2px 5px;
2623
2678
  border-radius: 4px;
2624
- font-weight: 600;
2625
- letter-spacing: 0.5px;
2679
+ font-weight: 500;
2680
+ letter-spacing: 0.25px;
2681
+ border: 1px solid rgba(251, 191, 36, 0.3);
2682
+ }
2683
+
2684
+ .fk-badge {
2685
+ font-size: 10px;
2686
+ background: rgba(96, 165, 250, 0.15);
2687
+ color: #60a5fa;
2688
+ padding: 2px 5px;
2689
+ border-radius: 4px;
2690
+ font-weight: 500;
2691
+ letter-spacing: 0.25px;
2692
+ border: 1px solid rgba(96, 165, 250, 0.3);
2626
2693
  }
2627
2694
 
2628
2695
  .col-type {
2629
- font-size: 12px;
2696
+ font-size: 11px;
2630
2697
  color: var(--text-muted);
2631
- font-family: monospace;
2698
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
2699
+ text-transform: uppercase;
2632
2700
  }
2633
2701
 
2634
2702
  td {
2635
- padding: 12px 20px;
2703
+ padding: 10px 16px;
2636
2704
  border-bottom: 1px solid var(--border-color);
2637
2705
  border-right: 1px solid var(--border-color);
2638
2706
  color: var(--text-main);
@@ -2640,13 +2708,11 @@ var htmlTemplate = `
2640
2708
  overflow: hidden;
2641
2709
  text-overflow: ellipsis;
2642
2710
  white-space: nowrap;
2643
- transition: background-color 0.2s ease;
2711
+ transition: background-color 0.15s ease;
2712
+ font-size: 13px;
2644
2713
  }
2645
2714
 
2646
2715
 
2647
- tr:last-child td {
2648
- border-bottom: none;
2649
- }
2650
2716
 
2651
2717
  tr:hover td {
2652
2718
  background-color: var(--row-hover);
@@ -2706,45 +2772,45 @@ var htmlTemplate = `
2706
2772
  display: flex;
2707
2773
  align-items: center;
2708
2774
  justify-content: space-between;
2709
- padding: 16px 24px;
2710
- background-color: rgba(24, 24, 27, 0.95);
2775
+ padding: 12px 16px;
2776
+ background-color: var(--panel-bg);
2711
2777
  border-top: 1px solid var(--border-color);
2712
- font-size: 14px;
2778
+ font-size: 13px;
2713
2779
  color: var(--text-muted);
2714
- position: sticky;
2715
- bottom: 0;
2716
- z-index: 5;
2780
+ width: fit-content;
2781
+ min-width: 100%;
2782
+ flex-shrink: 0;
2717
2783
  }
2718
2784
 
2719
2785
  .pagination-info {
2720
2786
  display: flex;
2721
2787
  align-items: center;
2722
- gap: 16px;
2788
+ gap: 12px;
2723
2789
  }
2724
2790
 
2725
2791
  .pagination-controls {
2726
2792
  display: flex;
2727
2793
  align-items: center;
2728
- gap: 8px;
2794
+ gap: 6px;
2729
2795
  }
2730
2796
 
2731
2797
  .pagination-btn {
2732
- background-color: rgba(255, 255, 255, 0.05);
2798
+ background-color: var(--input-bg);
2733
2799
  border: 1px solid var(--border-color);
2734
2800
  color: var(--text-main);
2735
2801
  padding: 6px 12px;
2736
2802
  border-radius: 6px;
2737
2803
  cursor: pointer;
2738
2804
  font-size: 13px;
2739
- transition: all 0.2s ease;
2805
+ transition: all 0.15s ease;
2740
2806
  }
2741
2807
 
2742
2808
  .pagination-btn:hover:not(:disabled) {
2743
- background-color: rgba(255, 255, 255, 0.1);
2809
+ background-color: var(--muted-bg);
2744
2810
  }
2745
2811
 
2746
2812
  .pagination-btn:disabled {
2747
- opacity: 0.4;
2813
+ opacity: 0.5;
2748
2814
  cursor: not-allowed;
2749
2815
  }
2750
2816
 
@@ -2754,7 +2820,7 @@ var htmlTemplate = `
2754
2820
  }
2755
2821
 
2756
2822
  .page-input {
2757
- background-color: rgba(255, 255, 255, 0.05);
2823
+ background-color: var(--input-bg);
2758
2824
  border: 1px solid var(--border-color);
2759
2825
  color: var(--text-main);
2760
2826
  padding: 6px 8px;
@@ -2763,6 +2829,20 @@ var htmlTemplate = `
2763
2829
  text-align: center;
2764
2830
  font-size: 13px;
2765
2831
  }
2832
+
2833
+ .page-size-select {
2834
+ background-color: var(--input-bg);
2835
+ border: 1px solid var(--border-color);
2836
+ color: var(--text-main);
2837
+ padding: 6px 8px;
2838
+ border-radius: 6px;
2839
+ font-size: 13px;
2840
+ cursor: pointer;
2841
+ }
2842
+
2843
+ .page-size-select:hover {
2844
+ background-color: var(--muted-bg);
2845
+ }
2766
2846
  </style>
2767
2847
  </head>
2768
2848
  <body>
@@ -2819,7 +2899,7 @@ var htmlTemplate = `
2819
2899
  let currentPage = 1;
2820
2900
  let totalPages = 1;
2821
2901
  let totalRecords = 0;
2822
- let pageSize = 50;
2902
+ let pageSize = 25;
2823
2903
 
2824
2904
  // Format values for the data grid
2825
2905
  function formatValue(val) {
@@ -2899,21 +2979,29 @@ var htmlTemplate = `
2899
2979
  document.getElementById('refresh-btn').disabled = false;
2900
2980
 
2901
2981
  const container = document.getElementById('data-container');
2902
- container.innerHTML = \`
2903
- <div class="empty-state">
2904
- <svg class="spinning" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2905
- <line x1="12" y1="2" x2="12" y2="6"></line>
2906
- <line x1="12" y1="18" x2="12" y2="22"></line>
2907
- <line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line>
2908
- <line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line>
2909
- <line x1="2" y1="12" x2="6" y2="12"></line>
2910
- <line x1="18" y1="12" x2="22" y2="12"></line>
2911
- <line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line>
2912
- <line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line>
2913
- </svg>
2914
- <p>Loading data...</p>
2915
- </div>
2916
- \`;
2982
+ const existingTable = container.querySelector('.data-grid-container');
2983
+
2984
+ if (existingTable) {
2985
+ // Show loading overlay instead of replacing content
2986
+ existingTable.style.opacity = '0.5';
2987
+ existingTable.style.pointerEvents = 'none';
2988
+ } else {
2989
+ container.innerHTML = \`
2990
+ <div class="empty-state">
2991
+ <svg class="spinning" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2992
+ <line x1="12" y1="2" x2="12" y2="6"></line>
2993
+ <line x1="12" y1="18" x2="12" y2="22"></line>
2994
+ <line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line>
2995
+ <line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line>
2996
+ <line x1="2" y1="12" x2="6" y2="12"></line>
2997
+ <line x1="18" y1="12" x2="22" y2="12"></line>
2998
+ <line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line>
2999
+ <line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line>
3000
+ </svg>
3001
+ <p>Loading data...</p>
3002
+ </div>
3003
+ \`;
3004
+ }
2917
3005
 
2918
3006
  try {
2919
3007
  const res = await fetch(\`/api/tables/\${name}/data?page=\${page}&limit=\${pageSize}\`);
@@ -2956,13 +3044,32 @@ var htmlTemplate = `
2956
3044
  });
2957
3045
  }
2958
3046
 
2959
- columns = Array.from(allKeys).map(key => ({
2960
- name: key,
2961
- type: schemaColMap[key] ? schemaColMap[key].type : typeof rows[0][key],
2962
- primaryKey: schemaColMap[key] ? schemaColMap[key].primaryKey : false
2963
- }));
3047
+ columns = Array.from(allKeys).map(key => {
3048
+ const schemaCol = schemaColMap[key];
3049
+ let type = 'text';
3050
+
3051
+ // First try to get type from schema
3052
+ if (schemaCol && schemaCol.type) {
3053
+ type = schemaCol.type;
3054
+ } else if (rows[0] && rows[0][key] !== undefined) {
3055
+ // Fallback to type detection from actual value
3056
+ const val = rows[0][key];
3057
+ if (typeof val === 'number') type = 'integer';
3058
+ else if (typeof val === 'boolean') type = 'boolean';
3059
+ else if (val instanceof Date) type = 'timestamptz';
3060
+ else if (typeof val === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(val)) type = 'uuid';
3061
+ else type = 'text';
3062
+ }
3063
+
3064
+ return {
3065
+ name: key,
3066
+ type,
3067
+ primaryKey: schemaCol ? schemaCol.primaryKey : false,
3068
+ foreignKey: schemaCol ? schemaCol.foreignKey : null
3069
+ };
3070
+ });
2964
3071
 
2965
- let html = '<div class="data-grid-container"><table><thead><tr>';
3072
+ let html = '<div class="data-grid-container"><div class="table-wrapper"><table><thead><tr>';
2966
3073
  columns.forEach(col => {
2967
3074
  html += \`
2968
3075
  <th>
@@ -2970,6 +3077,7 @@ var htmlTemplate = `
2970
3077
  <div class="col-name">
2971
3078
  \${col.name}
2972
3079
  \${col.primaryKey ? '<span class="pk-badge">PK</span>' : ''}
3080
+ \${col.foreignKey ? '<span class="fk-badge">FK</span>' : ''}
2973
3081
  </div>
2974
3082
  <div class="col-type">\${col.type}</div>
2975
3083
  </div>
@@ -2988,12 +3096,22 @@ var htmlTemplate = `
2988
3096
 
2989
3097
  html += '</tbody></table>';
2990
3098
 
2991
- // Add pagination
3099
+ // Add pagination inside the table wrapper to match table width
2992
3100
  html += renderPagination();
2993
3101
 
2994
- html += '</div>';
3102
+ html += '</div></div>';
2995
3103
  container.innerHTML = html;
2996
3104
 
3105
+ // Fade in the new content
3106
+ const newTable = container.querySelector('.data-grid-container');
3107
+ if (newTable) {
3108
+ newTable.style.opacity = '0';
3109
+ requestAnimationFrame(() => {
3110
+ newTable.style.transition = 'opacity 0.15s ease';
3111
+ newTable.style.opacity = '1';
3112
+ });
3113
+ }
3114
+
2997
3115
  } catch (e) {
2998
3116
  container.innerHTML = \`
2999
3117
  <div class="empty-state">
@@ -3015,18 +3133,27 @@ var htmlTemplate = `
3015
3133
 
3016
3134
  let html = '<div class="pagination">';
3017
3135
  html += '<div class="pagination-info">';
3018
- html += \`<span>Total: \${totalRecords} records</span>\`;
3019
- html += \`<span>Page \${currentPage} of \${totalPages}</span>\`;
3020
- html += \`<span>Showing \${startRecord}-\${endRecord}</span>\`;
3136
+ html += \`<span>\${totalRecords} records</span>\`;
3137
+ html += \`<span>\${startRecord}-\${endRecord}</span>\`;
3021
3138
  html += '</div>';
3022
3139
 
3023
3140
  html += '<div class="pagination-controls">';
3024
3141
 
3142
+ // Page size selector
3143
+ html += '<select class="page-size-select" onchange="changePageSize(this.value)">';
3144
+ [10, 25, 50, 100].forEach(size => {
3145
+ html += \`<option value="\${size}" \${pageSize === size ? 'selected' : ''}>\${size}</option>\`;
3146
+ });
3147
+ html += '</select>';
3148
+
3025
3149
  // Previous button
3026
- html += \`<button class="pagination-btn" onclick="changePage(\${currentPage - 1})" \${currentPage === 1 ? 'disabled' : ''}>Previous</button>\`;
3150
+ html += \`<button class="pagination-btn" onclick="changePage(\${currentPage - 1})" \${currentPage === 1 ? 'disabled' : ''}>\u2190</button>\`;
3151
+
3152
+ // Page indicator
3153
+ html += \`<span style="min-width: 60px; text-align: center;">\${currentPage} / \${totalPages}</span>\`;
3027
3154
 
3028
3155
  // Next button
3029
- html += \`<button class="pagination-btn" onclick="changePage(\${currentPage + 1})" \${currentPage === totalPages ? 'disabled' : ''}>Next</button>\`;
3156
+ html += \`<button class="pagination-btn" onclick="changePage(\${currentPage + 1})" \${currentPage === totalPages ? 'disabled' : ''}>\u2192</button>\`;
3030
3157
 
3031
3158
  html += '</div>';
3032
3159
  html += '</div>';
@@ -3034,6 +3161,12 @@ var htmlTemplate = `
3034
3161
  return html;
3035
3162
  }
3036
3163
 
3164
+ function changePageSize(newSize) {
3165
+ pageSize = parseInt(newSize, 10);
3166
+ currentPage = 1;
3167
+ selectTable(currentTable, currentPage);
3168
+ }
3169
+
3037
3170
  function changePage(page) {
3038
3171
  if (page < 1 || page > totalPages) return;
3039
3172
  selectTable(currentTable, page);
@@ -3069,7 +3202,7 @@ async function runTusky(config) {
3069
3202
  for (const s of schemas) {
3070
3203
  schemaObj[s.exportName] = s.table;
3071
3204
  }
3072
- const db = createDB({ url: config.dbUrl, schema: schemaObj });
3205
+ const db = bungres({ url: config.dbUrl, schema: schemaObj });
3073
3206
  console.log("=========================================");
3074
3207
  console.log("\uD83D\uDC18 Welcome to Bungres REPL (Tusky)");
3075
3208
  console.log("=========================================");
@@ -3127,7 +3260,7 @@ Example query: await db.select().from(users)`);
3127
3260
  }
3128
3261
 
3129
3262
  // src/config.ts
3130
- import { resolve as resolve7, join as join6 } from "path";
3263
+ import { join as join6, resolve as resolve7 } from "path";
3131
3264
  var CONFIG_FILES = [
3132
3265
  "bungres.config.ts",
3133
3266
  "bungres.config.js",
@@ -3144,7 +3277,7 @@ async function loadConfig(cwd = process.cwd()) {
3144
3277
  break;
3145
3278
  }
3146
3279
  }
3147
- const dbUrl = userConfig.dbCredentials?.url ?? process.env["DATABASE_URL"] ?? process.env["POSTGRES_URL"] ?? "";
3280
+ const dbUrl = userConfig.dbCredentials?.url ?? Bun.env.DATABASE_URL ?? Bun.env.POSTGRES_URL ?? "";
3148
3281
  if (!dbUrl) {
3149
3282
  console.error(`bungres: No database URL found.
3150
3283
  Add dbCredentials.url to bungres.config.ts or set DATABASE_URL.`);
@@ -3157,8 +3290,11 @@ async function loadConfig(cwd = process.cwd()) {
3157
3290
  out,
3158
3291
  dbUrl,
3159
3292
  dbSchema: userConfig.dbSchema ?? "public",
3160
- migrationsDir: out,
3161
- outDir: "./src/db/generated",
3293
+ migrationsTable: userConfig.migrations?.table ?? "__bungres_migrations",
3294
+ migrationsSchema: userConfig.migrations?.schema ?? "bungres",
3295
+ breakpoints: userConfig.breakpoints ?? true,
3296
+ strict: userConfig.strict ?? false,
3297
+ outDir: userConfig.outDir ?? "./src/db/generated",
3162
3298
  verbose: userConfig.verbose ?? false
3163
3299
  };
3164
3300
  }
@@ -3175,6 +3311,9 @@ async function ensureDatabase2(dbUrl) {
3175
3311
  try {
3176
3312
  const parsed = new URL(dbUrl);
3177
3313
  const dbName = parsed.pathname.slice(1);
3314
+ if (!/^[a-zA-Z_][a-zA-Z0-9_$]*$/.test(dbName)) {
3315
+ throw new Error(`Invalid database name: "${dbName}"`);
3316
+ }
3178
3317
  parsed.pathname = "/postgres";
3179
3318
  const fallbackUrl = parsed.toString();
3180
3319
  const fallback = new Bun.SQL(fallbackUrl);
@@ -3192,9 +3331,69 @@ async function ensureDatabase2(dbUrl) {
3192
3331
  }
3193
3332
  }
3194
3333
  }
3334
+ // package.json
3335
+ var package_default = {
3336
+ name: "@bungres/kit",
3337
+ version: "0.6.0",
3338
+ description: "CLI toolkit for @bungres/orm \u2014 migrate, push, generate, pull",
3339
+ license: "MIT",
3340
+ engines: {
3341
+ node: ">=24.0.0",
3342
+ bun: ">=1.3.0"
3343
+ },
3344
+ type: "module",
3345
+ bin: {
3346
+ bungres: "./dist/cli.js"
3347
+ },
3348
+ main: "./dist/index.js",
3349
+ types: "./dist/index.d.ts",
3350
+ exports: {
3351
+ ".": {
3352
+ bun: "./src/index.ts",
3353
+ import: "./dist/index.js",
3354
+ types: "./dist/index.d.ts"
3355
+ }
3356
+ },
3357
+ files: [
3358
+ "dist",
3359
+ "README.md",
3360
+ "LICENSE"
3361
+ ],
3362
+ author: "aniket khote",
3363
+ repository: {
3364
+ type: "git",
3365
+ url: "https://github.com/bungres/bungres.git",
3366
+ directory: "packages/bungres-kit"
3367
+ },
3368
+ bugs: {
3369
+ url: "https://github.com/bungres/bungres/issues"
3370
+ },
3371
+ homepage: "https://github.com/bungres/bungres#readme",
3372
+ keywords: [
3373
+ "bun",
3374
+ "postgres",
3375
+ "orm",
3376
+ "cli",
3377
+ "migration",
3378
+ "generator"
3379
+ ],
3380
+ scripts: {
3381
+ "build:types": "tsc --emitDeclarationOnly",
3382
+ build: "bun build ./src/cli.ts ./src/index.ts --outdir ./dist --target bun --format esm && npm run build:types",
3383
+ dev: "bun --watch src/cli.ts",
3384
+ test: "bun test"
3385
+ },
3386
+ dependencies: {
3387
+ "@bungres/orm": "workspace:*"
3388
+ },
3389
+ devDependencies: {
3390
+ "bun-types": "latest",
3391
+ typescript: "latest"
3392
+ }
3393
+ };
3195
3394
 
3196
3395
  // src/cli.ts
3197
- var VERSION = "0.0.1";
3396
+ var VERSION = package_default.version;
3198
3397
  var HELP = `
3199
3398
  ${colorize("\uD83D\uDC18 Bungres ORM CLI", "cyan")} v${VERSION}
3200
3399