@bungres/kit 0.3.0 → 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 +1450 -589
- 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 +556 -152
- 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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
// ../../node_modules/.bun/@bungres+orm@0.2.0/node_modules/@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
|
-
//
|
|
1021
|
+
// ../bungres-orm/src/core/db.ts
|
|
655
1022
|
function parseDBName(url) {
|
|
656
1023
|
try {
|
|
657
1024
|
return new URL(url).pathname.slice(1);
|
|
@@ -817,7 +1184,7 @@ function createDB(config2) {
|
|
|
817
1184
|
}
|
|
818
1185
|
return db2;
|
|
819
1186
|
}
|
|
820
|
-
//
|
|
1187
|
+
// ../bungres-orm/src/ddl.ts
|
|
821
1188
|
function generateCreateTable(config2, ifNotExists = true) {
|
|
822
1189
|
const tableName = config2.schema ? `"${config2.schema}"."${config2.name}"` : `"${config2.name}"`;
|
|
823
1190
|
const exists = ifNotExists ? " IF NOT EXISTS" : "";
|
|
@@ -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();
|
|
1621
|
+
} else {
|
|
1622
|
+
changes.push(`ALTER COLUMN ${col2} DROP DEFAULT`);
|
|
1623
|
+
}
|
|
1195
1624
|
}
|
|
1625
|
+
return changes;
|
|
1196
1626
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
return
|
|
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,
|
|
@@ -1625,126 +2079,76 @@ function toCamelCase(str) {
|
|
|
1625
2079
|
return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
1626
2080
|
}
|
|
1627
2081
|
|
|
1628
|
-
// src/commands/
|
|
1629
|
-
import
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
const migrationsDir = resolve6(config2.migrationsDir);
|
|
1633
|
-
const sql2 = new Bun.SQL(config2.dbUrl);
|
|
1634
|
-
try {
|
|
1635
|
-
const tableCheck = await sql2.unsafe(`SELECT EXISTS (
|
|
1636
|
-
SELECT 1 FROM information_schema.tables
|
|
1637
|
-
WHERE table_name = $1
|
|
1638
|
-
) AS exists`, [MIGRATIONS_TABLE2]);
|
|
1639
|
-
const trackingExists = tableCheck[0]?.exists ?? false;
|
|
1640
|
-
const glob = new Bun.Glob("*.sql");
|
|
1641
|
-
const files = [];
|
|
1642
|
-
for await (const file of glob.scan({ cwd: migrationsDir, absolute: false })) {
|
|
1643
|
-
files.push(file);
|
|
1644
|
-
}
|
|
1645
|
-
files.sort();
|
|
1646
|
-
if (files.length === 0) {
|
|
1647
|
-
console.log(colorize("No migration files found. Run `bungres generate` first.", "yellow"));
|
|
1648
|
-
return;
|
|
1649
|
-
}
|
|
1650
|
-
let appliedSet = new Set;
|
|
1651
|
-
if (trackingExists) {
|
|
1652
|
-
const applied = await sql2.unsafe(`SELECT name FROM "${MIGRATIONS_TABLE2}" ORDER BY applied_at`);
|
|
1653
|
-
appliedSet = new Set(applied.map((r) => r.name));
|
|
1654
|
-
}
|
|
1655
|
-
console.log(colorize(`
|
|
1656
|
-
Migration status:
|
|
1657
|
-
`, "cyan"));
|
|
1658
|
-
let pendingCount = 0;
|
|
1659
|
-
for (const file of files) {
|
|
1660
|
-
const isApplied = appliedSet.has(file);
|
|
1661
|
-
const status = isApplied ? colorize("\u2713 applied ", "green") : colorize("\u2717 pending ", "yellow");
|
|
1662
|
-
if (!isApplied)
|
|
1663
|
-
pendingCount++;
|
|
1664
|
-
console.log(` ${status} ${file}`);
|
|
1665
|
-
}
|
|
1666
|
-
console.log(`
|
|
1667
|
-
${colorize(appliedSet.size.toString(), "green")} applied, ${colorize(pendingCount.toString(), "yellow")} pending.
|
|
1668
|
-
`);
|
|
1669
|
-
} finally {
|
|
1670
|
-
await sql2.end();
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
// src/commands/drop.ts
|
|
1675
|
-
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...");
|
|
1676
2086
|
const schemas2 = await loadSchemas(config2.schema);
|
|
1677
2087
|
if (schemas2.length === 0) {
|
|
1678
|
-
console.warn("No table definitions found
|
|
2088
|
+
console.warn("No table definitions found. Check your schema glob pattern.");
|
|
1679
2089
|
return;
|
|
1680
2090
|
}
|
|
1681
2091
|
const sql2 = new Bun.SQL(config2.dbUrl);
|
|
1682
2092
|
try {
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
const
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
console.log("No tables to drop (they either don't exist or were already dropped).");
|
|
1694
|
-
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;
|
|
1695
2103
|
}
|
|
1696
|
-
const
|
|
1697
|
-
|
|
1698
|
-
|
|
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;
|
|
1699
2110
|
}
|
|
1700
|
-
|
|
1701
|
-
|
|
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!`);
|
|
1702
2122
|
}
|
|
1703
2123
|
if (!opts.force) {
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
console.log(
|
|
1709
|
-
|
|
1710
|
-
Are you sure? Type YES to continue: `, "cyan"));
|
|
1711
|
-
for await (const line2 of console) {
|
|
1712
|
-
if (line2.trim().toLowerCase() !== "yes") {
|
|
1713
|
-
console.log("Aborted.");
|
|
1714
|
-
return;
|
|
1715
|
-
}
|
|
1716
|
-
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;
|
|
1717
2130
|
}
|
|
1718
2131
|
}
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
}
|
|
1728
|
-
if (pushTableExists) {
|
|
1729
|
-
await sql2.unsafe("DROP TABLE IF EXISTS public.__bungres_push CASCADE");
|
|
1730
|
-
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);
|
|
1731
2140
|
}
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
console.log(`
|
|
1744
|
-
Re-running migrations...`);
|
|
1745
|
-
await runMigrate(config2);
|
|
1746
|
-
console.log(`
|
|
1747
|
-
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();
|
|
1748
2152
|
}
|
|
1749
2153
|
|
|
1750
2154
|
// src/commands/refresh.ts
|
|
@@ -1771,13 +2175,13 @@ Refresh complete. All tables are now empty.`, "green"));
|
|
|
1771
2175
|
}
|
|
1772
2176
|
|
|
1773
2177
|
// src/commands/seed.ts
|
|
1774
|
-
import { resolve as
|
|
2178
|
+
import { resolve as resolve5 } from "path";
|
|
1775
2179
|
async function runSeed(config2) {
|
|
1776
2180
|
if (!config2.seed) {
|
|
1777
2181
|
console.log(colorize(`No seed file configured in bungres.config.ts`, "yellow"));
|
|
1778
2182
|
return;
|
|
1779
2183
|
}
|
|
1780
|
-
const seedPath =
|
|
2184
|
+
const seedPath = resolve5(process.cwd(), config2.seed);
|
|
1781
2185
|
const file = Bun.file(seedPath);
|
|
1782
2186
|
if (!await file.exists()) {
|
|
1783
2187
|
console.error(colorize(`Seed file not found at ${seedPath}`, "red"));
|
|
@@ -1802,6 +2206,52 @@ Seed failed with exit code ${exitCode}.`);
|
|
|
1802
2206
|
}
|
|
1803
2207
|
}
|
|
1804
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
|
+
|
|
1805
2255
|
// src/commands/studio.ts
|
|
1806
2256
|
async function runStudio(config2) {
|
|
1807
2257
|
const schemas2 = await loadSchemas(config2.schema);
|
|
@@ -1840,8 +2290,19 @@ async function runStudio(config2) {
|
|
|
1840
2290
|
return new Response("Table not found", { status: 404 });
|
|
1841
2291
|
}
|
|
1842
2292
|
try {
|
|
1843
|
-
const
|
|
1844
|
-
|
|
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
|
+
}), {
|
|
1845
2306
|
headers: { "Content-Type": "application/json" }
|
|
1846
2307
|
});
|
|
1847
2308
|
} catch (e) {
|
|
@@ -1891,18 +2352,26 @@ var htmlTemplate = `
|
|
|
1891
2352
|
<meta charset="UTF-8">
|
|
1892
2353
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1893
2354
|
<title>Bungres Studio</title>
|
|
1894
|
-
<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">
|
|
1895
2356
|
<style>
|
|
1896
2357
|
:root {
|
|
1897
|
-
--bg-color: #
|
|
1898
|
-
--panel-bg:
|
|
1899
|
-
--border-color: #
|
|
1900
|
-
--text-main: #
|
|
1901
|
-
--text-muted: #
|
|
1902
|
-
--accent-color: #
|
|
1903
|
-
--accent-hover: #
|
|
1904
|
-
--header-bg:
|
|
1905
|
-
--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;
|
|
1906
2375
|
}
|
|
1907
2376
|
|
|
1908
2377
|
* { box-sizing: border-box; }
|
|
@@ -1910,7 +2379,7 @@ var htmlTemplate = `
|
|
|
1910
2379
|
body {
|
|
1911
2380
|
margin: 0;
|
|
1912
2381
|
padding: 0;
|
|
1913
|
-
font-family: '
|
|
2382
|
+
font-family: 'Outfit', -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
|
1914
2383
|
background-color: var(--bg-color);
|
|
1915
2384
|
color: var(--text-main);
|
|
1916
2385
|
display: flex;
|
|
@@ -1920,56 +2389,91 @@ var htmlTemplate = `
|
|
|
1920
2389
|
|
|
1921
2390
|
/* Sidebar */
|
|
1922
2391
|
.sidebar {
|
|
1923
|
-
width:
|
|
2392
|
+
width: 280px;
|
|
1924
2393
|
background-color: var(--panel-bg);
|
|
2394
|
+
backdrop-filter: blur(12px);
|
|
2395
|
+
-webkit-backdrop-filter: blur(12px);
|
|
1925
2396
|
border-right: 1px solid var(--border-color);
|
|
1926
2397
|
display: flex;
|
|
1927
2398
|
flex-direction: column;
|
|
2399
|
+
box-shadow: 4px 0 24px rgba(0,0,0,0.2);
|
|
2400
|
+
z-index: 20;
|
|
1928
2401
|
}
|
|
1929
2402
|
|
|
1930
2403
|
.sidebar-header {
|
|
1931
|
-
padding:
|
|
1932
|
-
border-bottom: 1px solid var(--border
|
|
2404
|
+
padding: 24px;
|
|
2405
|
+
border-bottom: 1px solid var(--glass-border);
|
|
1933
2406
|
display: flex;
|
|
1934
2407
|
align-items: center;
|
|
1935
|
-
gap:
|
|
2408
|
+
gap: 12px;
|
|
2409
|
+
background: linear-gradient(to bottom, rgba(255,255,255,0.03), transparent);
|
|
1936
2410
|
}
|
|
1937
2411
|
|
|
1938
2412
|
.sidebar-header h1 {
|
|
1939
2413
|
margin: 0;
|
|
1940
|
-
font-size:
|
|
2414
|
+
font-size: 18px;
|
|
1941
2415
|
font-weight: 600;
|
|
1942
|
-
|
|
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));
|
|
1943
2425
|
}
|
|
1944
2426
|
|
|
1945
2427
|
.table-list {
|
|
1946
2428
|
flex: 1;
|
|
1947
2429
|
overflow-y: auto;
|
|
1948
|
-
padding:
|
|
2430
|
+
padding: 16px 12px;
|
|
1949
2431
|
list-style: none;
|
|
1950
2432
|
margin: 0;
|
|
1951
2433
|
}
|
|
1952
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
|
+
|
|
1953
2443
|
.table-item {
|
|
1954
|
-
padding:
|
|
2444
|
+
padding: 12px 16px;
|
|
1955
2445
|
cursor: pointer;
|
|
1956
|
-
font-size:
|
|
2446
|
+
font-size: 15px;
|
|
2447
|
+
font-weight: 500;
|
|
1957
2448
|
color: var(--text-muted);
|
|
1958
|
-
|
|
2449
|
+
border-radius: 8px;
|
|
2450
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
1959
2451
|
display: flex;
|
|
1960
2452
|
align-items: center;
|
|
1961
|
-
gap:
|
|
2453
|
+
gap: 10px;
|
|
2454
|
+
margin-bottom: 4px;
|
|
2455
|
+
border: 1px solid transparent;
|
|
1962
2456
|
}
|
|
1963
2457
|
|
|
1964
2458
|
.table-item:hover {
|
|
1965
|
-
background-color:
|
|
2459
|
+
background-color: rgba(255, 255, 255, 0.05);
|
|
1966
2460
|
color: var(--text-main);
|
|
2461
|
+
transform: translateX(4px);
|
|
1967
2462
|
}
|
|
1968
2463
|
|
|
1969
2464
|
.table-item.active {
|
|
1970
|
-
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 {
|
|
1971
2475
|
color: var(--accent-color);
|
|
1972
|
-
|
|
2476
|
+
transform: scale(1.1);
|
|
1973
2477
|
}
|
|
1974
2478
|
|
|
1975
2479
|
/* Main Content */
|
|
@@ -1977,92 +2481,168 @@ var htmlTemplate = `
|
|
|
1977
2481
|
flex: 1;
|
|
1978
2482
|
display: flex;
|
|
1979
2483
|
flex-direction: column;
|
|
1980
|
-
|
|
2484
|
+
position: relative;
|
|
1981
2485
|
}
|
|
1982
2486
|
|
|
1983
2487
|
.main-header {
|
|
1984
|
-
height:
|
|
1985
|
-
padding: 0
|
|
2488
|
+
height: 70px;
|
|
2489
|
+
padding: 0 24px;
|
|
1986
2490
|
border-bottom: 1px solid var(--border-color);
|
|
1987
2491
|
display: flex;
|
|
1988
2492
|
align-items: center;
|
|
1989
2493
|
justify-content: space-between;
|
|
1990
2494
|
background-color: var(--header-bg);
|
|
2495
|
+
backdrop-filter: blur(12px);
|
|
2496
|
+
-webkit-backdrop-filter: blur(12px);
|
|
2497
|
+
z-index: 10;
|
|
1991
2498
|
}
|
|
1992
2499
|
|
|
1993
2500
|
.main-header h2 {
|
|
1994
2501
|
margin: 0;
|
|
1995
|
-
font-size:
|
|
1996
|
-
font-weight:
|
|
2502
|
+
font-size: 20px;
|
|
2503
|
+
font-weight: 600;
|
|
1997
2504
|
color: white;
|
|
2505
|
+
letter-spacing: -0.5px;
|
|
2506
|
+
display: flex;
|
|
2507
|
+
align-items: center;
|
|
2508
|
+
gap: 10px;
|
|
1998
2509
|
}
|
|
1999
2510
|
|
|
2000
2511
|
.btn {
|
|
2001
|
-
background-color:
|
|
2002
|
-
|
|
2003
|
-
|
|
2512
|
+
background-color: rgba(255, 255, 255, 0.05);
|
|
2513
|
+
border: 1px solid var(--border-color);
|
|
2514
|
+
color: var(--text-main);
|
|
2004
2515
|
padding: 6px 12px;
|
|
2005
2516
|
border-radius: 6px;
|
|
2006
|
-
font-size:
|
|
2517
|
+
font-size: 13px;
|
|
2007
2518
|
font-weight: 500;
|
|
2519
|
+
font-family: 'Outfit', sans-serif;
|
|
2008
2520
|
cursor: pointer;
|
|
2009
|
-
transition:
|
|
2521
|
+
transition: all 0.2s ease;
|
|
2010
2522
|
display: flex;
|
|
2011
2523
|
align-items: center;
|
|
2012
|
-
gap:
|
|
2524
|
+
gap: 8px;
|
|
2013
2525
|
}
|
|
2014
2526
|
|
|
2015
|
-
.btn:hover {
|
|
2016
|
-
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);
|
|
2017
2533
|
}
|
|
2018
2534
|
|
|
2019
2535
|
.btn:disabled {
|
|
2020
|
-
opacity: 0.
|
|
2536
|
+
opacity: 0.4;
|
|
2021
2537
|
cursor: not-allowed;
|
|
2022
2538
|
}
|
|
2023
2539
|
|
|
2024
2540
|
.content-area {
|
|
2025
2541
|
flex: 1;
|
|
2026
2542
|
overflow: auto;
|
|
2027
|
-
padding:
|
|
2543
|
+
padding: 0;
|
|
2544
|
+
position: relative;
|
|
2545
|
+
z-index: 1;
|
|
2546
|
+
text-align: left;
|
|
2028
2547
|
}
|
|
2029
2548
|
|
|
2030
2549
|
/* Data Table */
|
|
2031
2550
|
.data-grid-container {
|
|
2032
|
-
background-color:
|
|
2551
|
+
background-color: rgba(24, 24, 27, 0.5);
|
|
2033
2552
|
border: 1px solid var(--border-color);
|
|
2034
|
-
border-radius:
|
|
2035
|
-
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;
|
|
2036
2571
|
}
|
|
2037
2572
|
|
|
2038
2573
|
table {
|
|
2039
|
-
width:
|
|
2040
|
-
border-collapse:
|
|
2574
|
+
width: auto;
|
|
2575
|
+
border-collapse: separate;
|
|
2576
|
+
border-spacing: 0;
|
|
2041
2577
|
text-align: left;
|
|
2042
|
-
font-size:
|
|
2578
|
+
font-size: 14px;
|
|
2579
|
+
table-layout: auto;
|
|
2043
2580
|
}
|
|
2044
2581
|
|
|
2045
2582
|
th {
|
|
2046
|
-
background-color: rgba(
|
|
2047
|
-
|
|
2583
|
+
background-color: rgba(24, 24, 27, 0.95);
|
|
2584
|
+
backdrop-filter: blur(4px);
|
|
2585
|
+
color: var(--text-main);
|
|
2048
2586
|
font-weight: 500;
|
|
2049
|
-
padding:
|
|
2587
|
+
padding: 12px 20px;
|
|
2050
2588
|
border-bottom: 1px solid var(--border-color);
|
|
2589
|
+
border-right: 1px solid var(--border-color);
|
|
2051
2590
|
white-space: nowrap;
|
|
2052
2591
|
position: sticky;
|
|
2053
2592
|
top: 0;
|
|
2054
2593
|
z-index: 10;
|
|
2055
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
|
+
}
|
|
2056
2633
|
|
|
2057
2634
|
td {
|
|
2058
|
-
padding:
|
|
2635
|
+
padding: 12px 20px;
|
|
2059
2636
|
border-bottom: 1px solid var(--border-color);
|
|
2637
|
+
border-right: 1px solid var(--border-color);
|
|
2060
2638
|
color: var(--text-main);
|
|
2061
2639
|
max-width: 300px;
|
|
2062
2640
|
overflow: hidden;
|
|
2063
2641
|
text-overflow: ellipsis;
|
|
2064
2642
|
white-space: nowrap;
|
|
2643
|
+
transition: background-color 0.2s ease;
|
|
2065
2644
|
}
|
|
2645
|
+
|
|
2066
2646
|
|
|
2067
2647
|
tr:last-child td {
|
|
2068
2648
|
border-bottom: none;
|
|
@@ -2072,11 +2652,17 @@ var htmlTemplate = `
|
|
|
2072
2652
|
background-color: var(--row-hover);
|
|
2073
2653
|
}
|
|
2074
2654
|
|
|
2075
|
-
.type-number { color:
|
|
2076
|
-
.type-string { color:
|
|
2077
|
-
.type-boolean { color:
|
|
2078
|
-
.type-date { color:
|
|
2079
|
-
.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
|
+
}
|
|
2080
2666
|
|
|
2081
2667
|
.empty-state {
|
|
2082
2668
|
display: flex;
|
|
@@ -2084,7 +2670,98 @@ var htmlTemplate = `
|
|
|
2084
2670
|
align-items: center;
|
|
2085
2671
|
justify-content: center;
|
|
2086
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;
|
|
2087
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;
|
|
2088
2765
|
}
|
|
2089
2766
|
</style>
|
|
2090
2767
|
</head>
|
|
@@ -2092,7 +2769,7 @@ var htmlTemplate = `
|
|
|
2092
2769
|
|
|
2093
2770
|
<div class="sidebar">
|
|
2094
2771
|
<div class="sidebar-header">
|
|
2095
|
-
<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">
|
|
2096
2773
|
<ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
|
|
2097
2774
|
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
|
|
2098
2775
|
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
|
|
@@ -2106,19 +2783,31 @@ var htmlTemplate = `
|
|
|
2106
2783
|
|
|
2107
2784
|
<div class="main">
|
|
2108
2785
|
<div class="main-header">
|
|
2109
|
-
<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>
|
|
2110
2794
|
<button id="refresh-btn" class="btn" disabled>
|
|
2111
|
-
<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">
|
|
2112
2796
|
<polyline points="23 4 23 10 17 10"></polyline>
|
|
2113
2797
|
<polyline points="1 20 1 14 7 14"></polyline>
|
|
2114
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>
|
|
2115
2799
|
</svg>
|
|
2116
|
-
Refresh
|
|
2800
|
+
Refresh
|
|
2117
2801
|
</button>
|
|
2118
2802
|
</div>
|
|
2119
2803
|
<div class="content-area">
|
|
2120
2804
|
<div id="data-container" class="empty-state">
|
|
2121
|
-
<
|
|
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>
|
|
2122
2811
|
</div>
|
|
2123
2812
|
</div>
|
|
2124
2813
|
</div>
|
|
@@ -2126,21 +2815,38 @@ var htmlTemplate = `
|
|
|
2126
2815
|
<script>
|
|
2127
2816
|
let currentTable = null;
|
|
2128
2817
|
let tablesData = [];
|
|
2818
|
+
let tableSchemas = {};
|
|
2819
|
+
let currentPage = 1;
|
|
2820
|
+
let totalPages = 1;
|
|
2821
|
+
let totalRecords = 0;
|
|
2822
|
+
let pageSize = 50;
|
|
2129
2823
|
|
|
2130
2824
|
// Format values for the data grid
|
|
2131
2825
|
function formatValue(val) {
|
|
2132
|
-
if (val === null || val === undefined) return '
|
|
2133
|
-
|
|
2134
|
-
if (typeof val === '
|
|
2135
|
-
|
|
2136
|
-
|
|
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;
|
|
2137
2838
|
}
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
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);
|
|
2144
2850
|
}
|
|
2145
2851
|
|
|
2146
2852
|
async function loadTables() {
|
|
@@ -2150,12 +2856,14 @@ var htmlTemplate = `
|
|
|
2150
2856
|
|
|
2151
2857
|
const list = document.getElementById('table-list');
|
|
2152
2858
|
list.innerHTML = '';
|
|
2859
|
+
tableSchemas = {};
|
|
2153
2860
|
|
|
2154
2861
|
tablesData.forEach(t => {
|
|
2862
|
+
tableSchemas[t.name] = t;
|
|
2155
2863
|
const li = document.createElement('li');
|
|
2156
2864
|
li.className = 'table-item';
|
|
2157
2865
|
li.innerHTML = \`
|
|
2158
|
-
<svg width="
|
|
2866
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2159
2867
|
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
|
2160
2868
|
<line x1="3" y1="9" x2="21" y2="9"></line>
|
|
2161
2869
|
<line x1="9" y1="21" x2="9" y2="9"></line>
|
|
@@ -2170,8 +2878,9 @@ var htmlTemplate = `
|
|
|
2170
2878
|
}
|
|
2171
2879
|
}
|
|
2172
2880
|
|
|
2173
|
-
async function selectTable(name) {
|
|
2881
|
+
async function selectTable(name, page = 1) {
|
|
2174
2882
|
currentTable = name;
|
|
2883
|
+
currentPage = page;
|
|
2175
2884
|
|
|
2176
2885
|
// Update UI active state
|
|
2177
2886
|
document.querySelectorAll('.table-item').forEach(el => {
|
|
@@ -2179,60 +2888,169 @@ var htmlTemplate = `
|
|
|
2179
2888
|
else el.classList.remove('active');
|
|
2180
2889
|
});
|
|
2181
2890
|
|
|
2182
|
-
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
|
+
\`;
|
|
2183
2899
|
document.getElementById('refresh-btn').disabled = false;
|
|
2184
2900
|
|
|
2185
2901
|
const container = document.getElementById('data-container');
|
|
2186
|
-
container.innerHTML =
|
|
2187
|
-
|
|
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
|
+
\`;
|
|
2188
2917
|
|
|
2189
2918
|
try {
|
|
2190
|
-
const res = await fetch(\`/api/tables/\${name}/data\`);
|
|
2919
|
+
const res = await fetch(\`/api/tables/\${name}/data?page=\${page}&limit=\${pageSize}\`);
|
|
2191
2920
|
if (!res.ok) throw new Error(await res.text());
|
|
2192
|
-
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];
|
|
2193
2928
|
|
|
2194
2929
|
if (rows.length === 0) {
|
|
2195
|
-
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
|
+
\`;
|
|
2196
2941
|
return;
|
|
2197
2942
|
}
|
|
2198
2943
|
|
|
2199
|
-
|
|
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
|
+
}));
|
|
2200
2964
|
|
|
2201
2965
|
let html = '<div class="data-grid-container"><table><thead><tr>';
|
|
2202
2966
|
columns.forEach(col => {
|
|
2203
|
-
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
|
+
\`;
|
|
2204
2978
|
});
|
|
2205
2979
|
html += '</tr></thead><tbody>';
|
|
2206
2980
|
|
|
2207
2981
|
rows.forEach(row => {
|
|
2208
2982
|
html += '<tr>';
|
|
2209
2983
|
columns.forEach(col => {
|
|
2210
|
-
html += \`<td>\${formatValue(row[col])}</td>\`;
|
|
2984
|
+
html += \`<td>\${formatValue(row[col.name])}</td>\`;
|
|
2211
2985
|
});
|
|
2212
2986
|
html += '</tr>';
|
|
2213
2987
|
});
|
|
2214
2988
|
|
|
2215
|
-
html += '</tbody></table
|
|
2989
|
+
html += '</tbody></table>';
|
|
2990
|
+
|
|
2991
|
+
// Add pagination
|
|
2992
|
+
html += renderPagination();
|
|
2993
|
+
|
|
2994
|
+
html += '</div>';
|
|
2216
2995
|
container.innerHTML = html;
|
|
2217
2996
|
|
|
2218
2997
|
} catch (e) {
|
|
2219
|
-
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
|
+
\`;
|
|
2220
3009
|
}
|
|
2221
3010
|
}
|
|
2222
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
|
+
|
|
2223
3042
|
document.getElementById('refresh-btn').addEventListener('click', async () => {
|
|
2224
3043
|
if (!currentTable) return;
|
|
2225
3044
|
|
|
2226
|
-
const
|
|
2227
|
-
|
|
2228
|
-
btn.innerHTML = 'Refreshing...';
|
|
3045
|
+
const icon = document.getElementById('refresh-icon');
|
|
3046
|
+
icon.classList.add('spinning');
|
|
2229
3047
|
|
|
2230
3048
|
try {
|
|
2231
3049
|
await selectTable(currentTable);
|
|
2232
3050
|
} catch (e) {
|
|
2233
3051
|
console.error("Failed to refresh data", e);
|
|
2234
3052
|
} finally {
|
|
2235
|
-
setTimeout(() => {
|
|
3053
|
+
setTimeout(() => { icon.classList.remove('spinning'); }, 500);
|
|
2236
3054
|
}
|
|
2237
3055
|
});
|
|
2238
3056
|
|
|
@@ -2308,6 +3126,43 @@ Example query: await db.select().from(users)`);
|
|
|
2308
3126
|
});
|
|
2309
3127
|
}
|
|
2310
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
|
+
|
|
2311
3166
|
// src/ensure-db.ts
|
|
2312
3167
|
async function ensureDatabase2(dbUrl) {
|
|
2313
3168
|
let sql2;
|
|
@@ -2347,6 +3202,7 @@ ${colorize("Usage:", "yellow")}
|
|
|
2347
3202
|
bungres <command> [options]
|
|
2348
3203
|
|
|
2349
3204
|
${colorize("Commands:", "yellow")}
|
|
3205
|
+
init Initialize bungres project with config file and db folder structure
|
|
2350
3206
|
generate Generate SQL migration files from your schema definitions
|
|
2351
3207
|
migrate Run pending migration files against the database
|
|
2352
3208
|
push Apply schema directly to the DB (no migration files, dev mode)
|
|
@@ -2367,6 +3223,7 @@ ${colorize("Options:", "yellow")}
|
|
|
2367
3223
|
--help Show this help
|
|
2368
3224
|
|
|
2369
3225
|
${colorize("Examples:", "yellow")}
|
|
3226
|
+
bungres init
|
|
2370
3227
|
bungres generate
|
|
2371
3228
|
bungres migrate
|
|
2372
3229
|
bungres push
|
|
@@ -2391,6 +3248,10 @@ async function main() {
|
|
|
2391
3248
|
}
|
|
2392
3249
|
const command = args[0];
|
|
2393
3250
|
const flags = parseFlags(args.slice(1));
|
|
3251
|
+
if (command === "init") {
|
|
3252
|
+
await runInit();
|
|
3253
|
+
process.exit(0);
|
|
3254
|
+
}
|
|
2394
3255
|
const config2 = await loadConfig(process.cwd());
|
|
2395
3256
|
if (flags.verbose)
|
|
2396
3257
|
config2.verbose = true;
|