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