@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/README.md +4 -2
- package/dist/cli.js +393 -194
- package/dist/commands/drop.d.ts.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/studio.d.ts.map +1 -1
- package/dist/config.d.ts +37 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/ensure-db.d.ts.map +1 -1
- package/dist/index.js +109 -57
- package/package.json +2 -2
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("
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
982
|
+
ON "${junctionAlias}"."${rel.joinTargetColumn}" = "${subAlias}"."${getPkColumn(targetTableConfig)}"
|
|
952
983
|
`;
|
|
953
|
-
const whereExtra = `"${junctionAlias}"."${rel.joinSourceColumn}" = "${alias}"."${tableConfig
|
|
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
|
|
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(
|
|
1365
|
+
function colorize(text, color) {
|
|
1335
1366
|
const code2 = Bun.color(color, "ansi");
|
|
1336
|
-
return code2 ? `${code2}${
|
|
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
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
const
|
|
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(
|
|
1399
|
+
tableNamesToPrint.push(`${config2.migrationsSchema}.${config2.migrationsTable}`);
|
|
1364
1400
|
}
|
|
1365
1401
|
if (pushTableExists) {
|
|
1366
|
-
tableNamesToPrint.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
|
-
|
|
1391
|
-
|
|
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(
|
|
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
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
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(
|
|
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
|
|
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
|
|
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.
|
|
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:
|
|
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
|
-
|
|
1828
|
+
table,
|
|
1783
1829
|
unique,
|
|
1784
1830
|
index
|
|
1785
1831
|
} from "@bungres/orm";
|
|
1786
1832
|
|
|
1787
|
-
export const 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 {
|
|
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 =
|
|
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
|
-
`
|
|
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} =
|
|
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
|
|
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
|
|
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
|
|
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: { ...
|
|
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.
|
|
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 = $
|
|
2219
|
-
) AS exists`, [
|
|
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
|
|
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 =
|
|
2267
|
-
const port =
|
|
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
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
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:
|
|
2425
|
+
--panel-bg: #18181b;
|
|
2360
2426
|
--border-color: #27272a;
|
|
2361
|
-
--text-main: #
|
|
2427
|
+
--text-main: #fafafa;
|
|
2362
2428
|
--text-muted: #a1a1aa;
|
|
2363
2429
|
--accent-color: #8b5cf6;
|
|
2364
2430
|
--accent-hover: #7c3aed;
|
|
2365
|
-
--header-bg:
|
|
2366
|
-
--row-hover:
|
|
2367
|
-
--
|
|
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:
|
|
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:
|
|
2405
|
-
border-bottom: 1px solid var(--
|
|
2468
|
+
padding: 16px;
|
|
2469
|
+
border-bottom: 1px solid var(--border-color);
|
|
2406
2470
|
display: flex;
|
|
2407
2471
|
align-items: center;
|
|
2408
|
-
gap:
|
|
2409
|
-
background:
|
|
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:
|
|
2478
|
+
font-size: 14px;
|
|
2415
2479
|
font-weight: 600;
|
|
2416
|
-
|
|
2417
|
-
-
|
|
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:
|
|
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
|
|
2505
|
+
padding: 8px 12px;
|
|
2445
2506
|
cursor: pointer;
|
|
2446
|
-
font-size:
|
|
2507
|
+
font-size: 13px;
|
|
2447
2508
|
font-weight: 500;
|
|
2448
2509
|
color: var(--text-muted);
|
|
2449
|
-
border-radius:
|
|
2450
|
-
transition: all 0.
|
|
2510
|
+
border-radius: 6px;
|
|
2511
|
+
transition: all 0.15s ease;
|
|
2451
2512
|
display: flex;
|
|
2452
2513
|
align-items: center;
|
|
2453
|
-
gap:
|
|
2454
|
-
margin-bottom:
|
|
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:
|
|
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:
|
|
2525
|
+
background-color: var(--accent-color);
|
|
2466
2526
|
color: #fff;
|
|
2467
|
-
border:
|
|
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
|
-
|
|
2531
|
+
flex-shrink: 0;
|
|
2473
2532
|
}
|
|
2474
2533
|
.table-item.active svg {
|
|
2475
|
-
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:
|
|
2489
|
-
padding: 0
|
|
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:
|
|
2558
|
+
font-size: 14px;
|
|
2503
2559
|
font-weight: 600;
|
|
2504
|
-
color:
|
|
2505
|
-
letter-spacing: -0.
|
|
2560
|
+
color: var(--text-main);
|
|
2561
|
+
letter-spacing: -0.25px;
|
|
2506
2562
|
display: flex;
|
|
2507
2563
|
align-items: center;
|
|
2508
|
-
gap:
|
|
2564
|
+
gap: 8px;
|
|
2509
2565
|
}
|
|
2510
2566
|
|
|
2511
2567
|
.btn {
|
|
2512
|
-
background-color:
|
|
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.
|
|
2577
|
+
transition: all 0.15s ease;
|
|
2522
2578
|
display: flex;
|
|
2523
2579
|
align-items: center;
|
|
2524
|
-
gap:
|
|
2580
|
+
gap: 6px;
|
|
2525
2581
|
}
|
|
2526
2582
|
|
|
2527
2583
|
.btn:hover:not(:disabled) {
|
|
2528
|
-
background-color:
|
|
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.
|
|
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:
|
|
2607
|
+
background-color: var(--bg-color);
|
|
2552
2608
|
border: 1px solid var(--border-color);
|
|
2553
2609
|
border-radius: 0;
|
|
2554
|
-
overflow:
|
|
2555
|
-
|
|
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:
|
|
2584
|
-
backdrop-filter: blur(4px);
|
|
2646
|
+
background-color: var(--panel-bg);
|
|
2585
2647
|
color: var(--text-main);
|
|
2586
2648
|
font-weight: 500;
|
|
2587
|
-
|
|
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:
|
|
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.
|
|
2675
|
+
background: rgba(251, 191, 36, 0.15);
|
|
2621
2676
|
color: #fbbf24;
|
|
2622
|
-
padding: 2px
|
|
2677
|
+
padding: 2px 5px;
|
|
2623
2678
|
border-radius: 4px;
|
|
2624
|
-
font-weight:
|
|
2625
|
-
letter-spacing: 0.
|
|
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:
|
|
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:
|
|
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.
|
|
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
|
|
2710
|
-
background-color:
|
|
2775
|
+
padding: 12px 16px;
|
|
2776
|
+
background-color: var(--panel-bg);
|
|
2711
2777
|
border-top: 1px solid var(--border-color);
|
|
2712
|
-
font-size:
|
|
2778
|
+
font-size: 13px;
|
|
2713
2779
|
color: var(--text-muted);
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
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:
|
|
2788
|
+
gap: 12px;
|
|
2723
2789
|
}
|
|
2724
2790
|
|
|
2725
2791
|
.pagination-controls {
|
|
2726
2792
|
display: flex;
|
|
2727
2793
|
align-items: center;
|
|
2728
|
-
gap:
|
|
2794
|
+
gap: 6px;
|
|
2729
2795
|
}
|
|
2730
2796
|
|
|
2731
2797
|
.pagination-btn {
|
|
2732
|
-
background-color:
|
|
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.
|
|
2805
|
+
transition: all 0.15s ease;
|
|
2740
2806
|
}
|
|
2741
2807
|
|
|
2742
2808
|
.pagination-btn:hover:not(:disabled) {
|
|
2743
|
-
background-color:
|
|
2809
|
+
background-color: var(--muted-bg);
|
|
2744
2810
|
}
|
|
2745
2811
|
|
|
2746
2812
|
.pagination-btn:disabled {
|
|
2747
|
-
opacity: 0.
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
<
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
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
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
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
|
|
3019
|
-
html += \`<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' : ''}
|
|
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' : ''}
|
|
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 =
|
|
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 {
|
|
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 ??
|
|
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
|
-
|
|
3161
|
-
|
|
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 =
|
|
3396
|
+
var VERSION = package_default.version;
|
|
3198
3397
|
var HELP = `
|
|
3199
3398
|
${colorize("\uD83D\uDC18 Bungres ORM CLI", "cyan")} v${VERSION}
|
|
3200
3399
|
|