admindb 2.3.0 → 2.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.
Files changed (45) hide show
  1. package/README.md +17 -17
  2. package/dist/app.js +123 -163
  3. package/dist/cli/args.d.ts +1 -1
  4. package/dist/cli/args.js +5 -3
  5. package/dist/cli/config.d.ts +1 -1
  6. package/dist/cli/config.js +9 -2
  7. package/dist/cli/runner.js +9 -6
  8. package/dist/db/manager.d.ts +1 -1
  9. package/dist/db/manager.js +21 -2
  10. package/dist/public/css/app.css +1 -1
  11. package/dist/public/js/browse.js +1 -1
  12. package/dist/public/js/databases.js +1 -1
  13. package/dist/public/js/designer.js +1 -1
  14. package/dist/public/js/forms.js +1 -1
  15. package/dist/public/js/home.js +1 -1
  16. package/dist/public/js/inspector.js +1 -1
  17. package/dist/public/js/query.js +1 -1
  18. package/dist/public/js/schema.js +1 -1
  19. package/dist/public/js/seed.js +1 -1
  20. package/dist/routes/api/erd.js +2 -2
  21. package/dist/routes/api/helpers.d.ts +1 -1
  22. package/dist/routes/api/import-export.js +8 -6
  23. package/dist/routes/api/index.js +16 -9
  24. package/dist/routes/api/query.js +8 -5
  25. package/dist/routes/api/rows.js +36 -21
  26. package/dist/routes/api/seed.js +29 -16
  27. package/dist/routes/api/tables.js +32 -18
  28. package/dist/routes/databases.js +2 -0
  29. package/dist/routes/pages.d.ts +1 -1
  30. package/dist/routes/pages.js +137 -43
  31. package/dist/serverless.js +16 -4
  32. package/dist/views/layouts/main.hbs +1 -1
  33. package/dist/views/pages/databases.hbs +1 -1
  34. package/dist/views/pages/erd.hbs +1 -1
  35. package/dist/views/pages/form.hbs +1 -1
  36. package/dist/views/pages/home.hbs +1 -1
  37. package/dist/views/pages/info.hbs +1 -1
  38. package/dist/views/pages/schema.hbs +1 -1
  39. package/dist/views/pages/seed-select.hbs +6 -5
  40. package/dist/views/pages/seed.hbs +3 -3
  41. package/dist/views/pages/table.hbs +1 -1
  42. package/dist/views/partials/navbar.hbs +1 -1
  43. package/dist/views/partials/sidebar.hbs +1 -1
  44. package/docs/API.md +60 -55
  45. package/package.json +3 -2
@@ -4,20 +4,22 @@ exports.registerTableRoutes = registerTableRoutes;
4
4
  const generator_1 = require("../../sql/generator");
5
5
  const helpers_1 = require("./helpers");
6
6
  function registerTableRoutes(router, ctx) {
7
- const { db } = ctx;
8
- router.get('/api/tables', (0, helpers_1.wrap)(async (_req, res) => {
7
+ router.get('/api/tables/:db', (0, helpers_1.wrap)(async (req, res) => {
8
+ const db = ctx.getDb(req);
9
9
  const r = await db.listTables();
10
10
  if (!r.success)
11
11
  return (0, helpers_1.fail)(res, r.error ?? 'Failed to list tables.', 500);
12
12
  (0, helpers_1.ok)(res, r.data);
13
13
  }));
14
- router.get('/api/tables/:table/info', (0, helpers_1.wrap)(async (req, res) => {
14
+ router.get('/api/tables/:db/:table/info', (0, helpers_1.wrap)(async (req, res) => {
15
+ const db = ctx.getDb(req);
15
16
  const info = await (0, helpers_1.requireTable)(db, req.params.table);
16
17
  if (!info)
17
18
  return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
18
19
  (0, helpers_1.ok)(res, info);
19
20
  }));
20
- router.get('/api/tables/:table/fk-options', (0, helpers_1.wrap)(async (req, res) => {
21
+ router.get('/api/tables/:db/:table/fk-options', (0, helpers_1.wrap)(async (req, res) => {
22
+ const db = ctx.getDb(req);
21
23
  const info = await (0, helpers_1.requireTable)(db, req.params.table);
22
24
  if (!info)
23
25
  return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
@@ -42,7 +44,8 @@ function registerTableRoutes(router, ctx) {
42
44
  }
43
45
  (0, helpers_1.ok)(res, options);
44
46
  }));
45
- router.get('/api/tables/:table/ddl', (0, helpers_1.wrap)(async (req, res) => {
47
+ router.get('/api/tables/:db/:table/ddl', (0, helpers_1.wrap)(async (req, res) => {
48
+ const db = ctx.getDb(req);
46
49
  const r = await db.getCreateStatement(req.params.table);
47
50
  if (!r.success)
48
51
  return (0, helpers_1.fail)(res, r.error ?? 'Failed to fetch DDL.', 500);
@@ -50,7 +53,8 @@ function registerTableRoutes(router, ctx) {
50
53
  return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
51
54
  (0, helpers_1.ok)(res, { sql: r.data });
52
55
  }));
53
- router.post('/api/tables/:table/rename', (0, helpers_1.wrap)(async (req, res) => {
56
+ router.post('/api/tables/:db/:table/rename', (0, helpers_1.wrap)(async (req, res) => {
57
+ const db = ctx.getDb(req);
54
58
  const newName = String(req.body?.name ?? '').trim();
55
59
  if (!newName)
56
60
  return (0, helpers_1.fail)(res, 'New table name is required.');
@@ -59,13 +63,15 @@ function registerTableRoutes(router, ctx) {
59
63
  return (0, helpers_1.fail)(res, r.error ?? 'Failed to rename table.');
60
64
  (0, helpers_1.ok)(res, { message: `Table renamed to "${newName}".`, name: newName });
61
65
  }));
62
- router.delete('/api/tables/:table', (0, helpers_1.wrap)(async (req, res) => {
66
+ router.delete('/api/tables/:db/:table', (0, helpers_1.wrap)(async (req, res) => {
67
+ const db = ctx.getDb(req);
63
68
  const r = await db.dropTable(req.params.table);
64
69
  if (!r.success)
65
70
  return (0, helpers_1.fail)(res, r.error ?? 'Failed to drop table.');
66
71
  (0, helpers_1.ok)(res, { message: `Table "${req.params.table}" dropped.` });
67
72
  }));
68
- router.post('/api/tables/bulk-drop', (0, helpers_1.wrap)(async (req, res) => {
73
+ router.post('/api/tables/:db/bulk-drop', (0, helpers_1.wrap)(async (req, res) => {
74
+ const db = ctx.getDb(req);
69
75
  const tables = Array.isArray(req.body?.tables)
70
76
  ? req.body.tables.filter((t) => typeof t === 'string' && t.trim().length > 0)
71
77
  : [];
@@ -97,7 +103,8 @@ function registerTableRoutes(router, ctx) {
97
103
  message: `Dropped ${dropped.length} table(s)${failed.length ? `, ${failed.length} failed` : ''}.`,
98
104
  });
99
105
  }));
100
- router.post('/api/tables/bulk-truncate', (0, helpers_1.wrap)(async (req, res) => {
106
+ router.post('/api/tables/:db/bulk-truncate', (0, helpers_1.wrap)(async (req, res) => {
107
+ const db = ctx.getDb(req);
101
108
  const tables = Array.isArray(req.body?.tables)
102
109
  ? req.body.tables.filter((t) => typeof t === 'string' && t.trim().length > 0)
103
110
  : [];
@@ -129,7 +136,7 @@ function registerTableRoutes(router, ctx) {
129
136
  message: `Cleared ${cleared.length} table(s)${failed.length ? `, ${failed.length} failed` : ''}.`,
130
137
  });
131
138
  }));
132
- router.post('/api/tables/generate', (0, helpers_1.wrap)(async (req, res) => {
139
+ router.post('/api/tables/:db/generate', (0, helpers_1.wrap)(async (req, res) => {
133
140
  const name = String(req.body?.name ?? '').trim();
134
141
  const columns = (req.body?.columns ?? []);
135
142
  if (!name)
@@ -144,6 +151,7 @@ function registerTableRoutes(router, ctx) {
144
151
  }
145
152
  }));
146
153
  const createTableHandler = (0, helpers_1.wrap)(async (req, res) => {
154
+ const db = ctx.getDb(req);
147
155
  const name = String(req.body?.name ?? '').trim();
148
156
  const columns = (req.body?.columns ?? []);
149
157
  if (!name)
@@ -162,9 +170,10 @@ function registerTableRoutes(router, ctx) {
162
170
  return (0, helpers_1.fail)(res, r.error ?? 'Failed to create table.');
163
171
  (0, helpers_1.ok)(res, { message: `Table "${name}" created.`, name }, 201);
164
172
  });
165
- router.post('/api/tables', createTableHandler);
166
- router.post('/api/tables/create', createTableHandler);
167
- router.post('/api/tables/:table/columns', (0, helpers_1.wrap)(async (req, res) => {
173
+ router.post('/api/tables/:db', createTableHandler);
174
+ router.post('/api/tables/:db/create', createTableHandler);
175
+ router.post('/api/tables/:db/:table/columns', (0, helpers_1.wrap)(async (req, res) => {
176
+ const db = ctx.getDb(req);
168
177
  const col = req.body;
169
178
  if (!col?.name)
170
179
  return (0, helpers_1.fail)(res, 'Column name is required.');
@@ -173,7 +182,8 @@ function registerTableRoutes(router, ctx) {
173
182
  return (0, helpers_1.fail)(res, r.error ?? 'Failed to add column.');
174
183
  (0, helpers_1.ok)(res, { message: `Column "${col.name}" added.` }, 201);
175
184
  }));
176
- router.put('/api/tables/:table/columns/:column', (0, helpers_1.wrap)(async (req, res) => {
185
+ router.put('/api/tables/:db/:table/columns/:column', (0, helpers_1.wrap)(async (req, res) => {
186
+ const db = ctx.getDb(req);
177
187
  const col = req.body;
178
188
  if (!col)
179
189
  return (0, helpers_1.fail)(res, 'Column definition is required.');
@@ -182,7 +192,8 @@ function registerTableRoutes(router, ctx) {
182
192
  return (0, helpers_1.fail)(res, r.error ?? 'Failed to modify column.');
183
193
  (0, helpers_1.ok)(res, { message: `Column "${req.params.column}" updated.` });
184
194
  }));
185
- router.post('/api/tables/:table/columns/:column/rename', (0, helpers_1.wrap)(async (req, res) => {
195
+ router.post('/api/tables/:db/:table/columns/:column/rename', (0, helpers_1.wrap)(async (req, res) => {
196
+ const db = ctx.getDb(req);
186
197
  const newName = String(req.body?.name ?? '').trim();
187
198
  if (!newName)
188
199
  return (0, helpers_1.fail)(res, 'New column name is required.');
@@ -191,13 +202,15 @@ function registerTableRoutes(router, ctx) {
191
202
  return (0, helpers_1.fail)(res, r.error ?? 'Failed to rename column.');
192
203
  (0, helpers_1.ok)(res, { message: `Column renamed to "${newName}".`, name: newName });
193
204
  }));
194
- router.delete('/api/tables/:table/columns/:column', (0, helpers_1.wrap)(async (req, res) => {
205
+ router.delete('/api/tables/:db/:table/columns/:column', (0, helpers_1.wrap)(async (req, res) => {
206
+ const db = ctx.getDb(req);
195
207
  const r = await db.dropColumn(req.params.table, req.params.column);
196
208
  if (!r.success)
197
209
  return (0, helpers_1.fail)(res, r.error ?? 'Failed to drop column.');
198
210
  (0, helpers_1.ok)(res, { message: `Column "${req.params.column}" dropped.` });
199
211
  }));
200
- router.post('/api/tables/:table/indexes', (0, helpers_1.wrap)(async (req, res) => {
212
+ router.post('/api/tables/:db/:table/indexes', (0, helpers_1.wrap)(async (req, res) => {
213
+ const db = ctx.getDb(req);
201
214
  const def = req.body;
202
215
  if (!def || !Array.isArray(def.columns) || def.columns.length === 0) {
203
216
  return (0, helpers_1.fail)(res, 'At least one column is required for an index.');
@@ -207,7 +220,8 @@ function registerTableRoutes(router, ctx) {
207
220
  return (0, helpers_1.fail)(res, r.error ?? 'Failed to create index.');
208
221
  (0, helpers_1.ok)(res, { message: 'Index created.' }, 201);
209
222
  }));
210
- router.delete('/api/tables/:table/indexes/:index', (0, helpers_1.wrap)(async (req, res) => {
223
+ router.delete('/api/tables/:db/:table/indexes/:index', (0, helpers_1.wrap)(async (req, res) => {
224
+ const db = ctx.getDb(req);
211
225
  const r = await db.dropIndex(req.params.index);
212
226
  if (!r.success)
213
227
  return (0, helpers_1.fail)(res, r.error ?? 'Failed to drop index.');
@@ -40,8 +40,10 @@ function registerDatabasesRoutes(router, ctx) {
40
40
  };
41
41
  router.get('/', async (_req, res, next) => {
42
42
  try {
43
+ res.locals.databasesMode = true;
43
44
  res.render('pages/databases', {
44
45
  title: 'Databases',
46
+ databasesMode: true,
45
47
  databases: await getDatabaseRows(manager),
46
48
  dbDir: manager.directory,
47
49
  readonly: Boolean(ctx.readonly),
@@ -1 +1 @@
1
- import type {Router} from 'express';import type {IDatabase} from '../db/index';import type {Logger} from '../utils/logger';interface PageContext{db:IDatabase;logger:Logger;}export declare function registerPages(router:Router,ctx:PageContext):void;export{};
1
+ import type {Router,Request} from 'express';import type {IDatabase} from '../db/index';import type {Logger} from '../utils/logger';export interface PageContext{getDb:(req:Request)=>IDatabase;logger:Logger;}export declare function registerPages(router:Router,ctx:PageContext):void;
@@ -6,7 +6,7 @@ const generator_1 = require("../sql/generator");
6
6
  const common_1 = require("../utils/common");
7
7
  const datatype_1 = require("../utils/datatype");
8
8
  const index_1 = require("../data/index");
9
- function buildDisplayRows(rawRows, info, table, basePath = '') {
9
+ function buildDisplayRows(rawRows, info, dbId, table, basePath = '') {
10
10
  const pkCols = info.primaryKey.length ? info.primaryKey : ['_rowid_'];
11
11
  return rawRows.map((row) => {
12
12
  const pkEncoded = (0, common_1.encodePk)(pkCols.map((c) => row[c]));
@@ -49,8 +49,8 @@ function buildDisplayRows(rawRows, info, table, basePath = '') {
49
49
  isAudio = mimeInfo.isAudio;
50
50
  isVideo = mimeInfo.isVideo;
51
51
  isPdf = mimeInfo.isPdf;
52
- if (table && pkEncoded) {
53
- blobUrl = `${basePath}/api/tables/${encodeURIComponent(table)}/row/${encodeURIComponent(pkEncoded)}/blob/${encodeURIComponent(c.name)}`;
52
+ if (table && pkEncoded && dbId) {
53
+ blobUrl = `${basePath}/api/tables/${encodeURIComponent(dbId)}/${encodeURIComponent(table)}/row/${encodeURIComponent(pkEncoded)}/blob/${encodeURIComponent(c.name)}`;
54
54
  }
55
55
  }
56
56
  else if ((0, datatype_1.isJsonString)(v) || typeUpper.includes('JSON')) {
@@ -87,15 +87,32 @@ function buildDisplayRows(rawRows, info, table, basePath = '') {
87
87
  };
88
88
  });
89
89
  }
90
+ function initPageLocals(res, db, dbId, allNames) {
91
+ const tables = allNames.filter((n) => !n.startsWith('_'));
92
+ const internalTables = allNames.filter((n) => n.startsWith('_'));
93
+ res.locals.dbId = dbId;
94
+ res.locals.dbPath = db.path;
95
+ res.locals.tables = tables;
96
+ res.locals.internalTables = internalTables;
97
+ res.locals.dialect = db.dialect;
98
+ res.locals.isPostgres = db.dialect === 'postgres';
99
+ res.locals.isSqlite = db.dialect === 'sqlite';
100
+ res.locals.dialectName = db.dialect === 'postgres' ? 'PostgreSQL' : 'SQLite';
101
+ res.locals.readonly = db.isReadOnly;
102
+ res.locals.databasesMode = false;
103
+ return { tables, internalTables };
104
+ }
90
105
  function registerPages(router, ctx) {
91
- const { db } = ctx;
92
106
  const notFound = (res, message) => {
93
107
  res.status(404).render('pages/error', { title: 'Not found', status: 404, error: message });
94
108
  };
95
- router.get('/', async (_req, res, next) => {
109
+ router.get('/home/:db', async (req, res, next) => {
96
110
  try {
97
- const names = res.locals.tables ?? [];
98
- const internalNames = res.locals.internalTables ?? [];
111
+ const db = ctx.getDb(req);
112
+ const dbId = req.params.db;
113
+ const all = await db.listTables();
114
+ const allNames = (all.data ?? []).map((t) => t.name);
115
+ const { tables: names, internalTables: internalNames } = initPageLocals(res, db, dbId, allNames);
99
116
  const colCountsMap = {};
100
117
  if (db.dialect === 'postgres') {
101
118
  const colRes = await db.all(`SELECT table_name, count(*)::int AS cols FROM information_schema.columns WHERE table_schema = 'public' GROUP BY table_name;`);
@@ -131,6 +148,7 @@ function registerPages(router, ctx) {
131
148
  }
132
149
  res.render('pages/home', {
133
150
  title: 'Home',
151
+ dbId,
134
152
  tables: names,
135
153
  counts,
136
154
  cols,
@@ -144,9 +162,15 @@ function registerPages(router, ctx) {
144
162
  next(err);
145
163
  }
146
164
  });
147
- router.get('/tables/:table', async (req, res, next) => {
165
+ router.get('/tables/:db/:table', async (req, res, next) => {
148
166
  try {
167
+ const db = ctx.getDb(req);
168
+ const dbId = req.params.db;
149
169
  const table = req.params.table;
170
+ res.locals.dbId = dbId;
171
+ const all = await db.listTables();
172
+ const allNames = (all.data ?? []).map((t) => t.name);
173
+ initPageLocals(res, db, dbId, allNames);
150
174
  const info = await db.getTableInfo(table);
151
175
  if (!info.success || !info.data || info.data.columns.length === 0) {
152
176
  return notFound(res, `Table "${table}" does not exist.`);
@@ -177,7 +201,7 @@ function registerPages(router, ctx) {
177
201
  type: c.type,
178
202
  active,
179
203
  dir: active ? orderDir : null,
180
- href: `${basePath}/tables/${encodeURIComponent(table)}?${qs}${filterQS ? `&${filterQS}` : ''}`,
204
+ href: `${basePath}/tables/${encodeURIComponent(dbId)}/${encodeURIComponent(table)}?${qs}${filterQS ? `&${filterQS}` : ''}`,
181
205
  };
182
206
  });
183
207
  const refColumns = (refsR.data ?? [])
@@ -197,7 +221,7 @@ function registerPages(router, ctx) {
197
221
  return [col.table, map];
198
222
  }));
199
223
  const countMap = new Map(countEntries);
200
- const rows = buildDisplayRows(rawRows, info.data, table, basePath).map((dr, i) => {
224
+ const rows = buildDisplayRows(rawRows, info.data, dbId, table, basePath).map((dr, i) => {
201
225
  const raw = rawRows[i];
202
226
  const refs = refColumns.map((col) => {
203
227
  const v = raw[col.to];
@@ -214,6 +238,7 @@ function registerPages(router, ctx) {
214
238
  res.locals.currentTable = table;
215
239
  res.render('pages/table', {
216
240
  title: table,
241
+ dbId,
217
242
  table,
218
243
  info: info.data,
219
244
  rows,
@@ -234,6 +259,7 @@ function registerPages(router, ctx) {
234
259
  firstRow: count === 0 ? 0 : (page - 1) * pageSize + 1,
235
260
  lastRow: Math.min(page * pageSize, count),
236
261
  browseConfig: {
262
+ dbId,
237
263
  table,
238
264
  filters,
239
265
  pkCols: info.data.primaryKey.length ? info.data.primaryKey : ['_rowid_'],
@@ -246,9 +272,15 @@ function registerPages(router, ctx) {
246
272
  next(err);
247
273
  }
248
274
  });
249
- router.get('/tables/:table/schema', async (req, res, next) => {
275
+ router.get('/schema/:db/:table', async (req, res, next) => {
250
276
  try {
277
+ const db = ctx.getDb(req);
278
+ const dbId = req.params.db;
251
279
  const table = req.params.table;
280
+ res.locals.dbId = dbId;
281
+ const all = await db.listTables();
282
+ const allNames = (all.data ?? []).map((t) => t.name);
283
+ initPageLocals(res, db, dbId, allNames);
252
284
  const [info, schema] = await Promise.all([db.getTableInfo(table), db.getSchema(table)]);
253
285
  if (!info.success || !info.data || info.data.columns.length === 0) {
254
286
  return notFound(res, `Table "${table}" does not exist.`);
@@ -259,26 +291,34 @@ function registerPages(router, ctx) {
259
291
  res.locals.currentTable = table;
260
292
  res.render('pages/schema', {
261
293
  title: `Schema · ${table}`,
294
+ dbId,
262
295
  table,
263
296
  schema: schema.data,
264
297
  isInternal: table.startsWith('_'),
265
298
  dialect: db.dialect,
266
299
  isSqlite: db.dialect === 'sqlite',
267
300
  isPostgres: db.dialect === 'postgres',
268
- config: { table, isInternal: table.startsWith('_'), dialect: db.dialect },
301
+ config: { dbId, table, isInternal: table.startsWith('_'), dialect: db.dialect },
269
302
  });
270
303
  }
271
304
  catch (err) {
272
305
  next(err);
273
306
  }
274
307
  });
275
- router.get('/seed', async (req, res, next) => {
308
+ router.get('/seed/:db', async (req, res, next) => {
276
309
  try {
277
- const tableNames = res.locals.tables ?? [];
310
+ const db = ctx.getDb(req);
311
+ const dbId = req.params.db;
312
+ res.locals.dbId = dbId;
313
+ const all = await db.listTables();
314
+ const allNames = (all.data ?? []).map((t) => t.name);
315
+ const { tables: tableNames } = initPageLocals(res, db, dbId, allNames);
278
316
  if (tableNames.length === 0) {
279
317
  return res.render('pages/seed-select', {
280
318
  title: 'Seed Data Generator',
281
- tables: [],
319
+ dbId,
320
+ tables: tableNames,
321
+ seedTables: [],
282
322
  hasTables: false,
283
323
  dialect: db.dialect,
284
324
  });
@@ -299,7 +339,9 @@ function registerPages(router, ctx) {
299
339
  const defaultMode = req.query.mode === 'chain' ? 'chain' : 'single';
300
340
  res.render('pages/seed-select', {
301
341
  title: 'Seed Data Generator · Choose Table & Mode',
302
- tables: tableStats,
342
+ dbId,
343
+ tables: tableNames,
344
+ seedTables: tableStats,
303
345
  hasTables: true,
304
346
  defaultTable,
305
347
  defaultMode,
@@ -310,16 +352,15 @@ function registerPages(router, ctx) {
310
352
  next(err);
311
353
  }
312
354
  });
313
- router.get('/tables/:table/seed', (req, res) => {
314
- const table = req.params.table;
315
- const qs = req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '';
316
- const bp = res.locals.basePath || '';
317
- return res.redirect(301, `${bp}/seed/${encodeURIComponent(table)}${qs}`);
318
- });
319
- router.get('/seed/:table', async (req, res, next) => {
355
+ router.get('/seed/:db/:table', async (req, res, next) => {
320
356
  try {
357
+ const db = ctx.getDb(req);
358
+ const dbId = req.params.db;
321
359
  const table = req.params.table;
322
- const allTables = res.locals.tables ?? [];
360
+ res.locals.dbId = dbId;
361
+ const all = await db.listTables();
362
+ const allNames = (all.data ?? []).map((t) => t.name);
363
+ const { tables: allTables } = initPageLocals(res, db, dbId, allNames);
323
364
  const initialMode = req.query.mode === 'chain' ? 'chain' : 'single';
324
365
  const [info, countRes] = await Promise.all([db.getTableInfo(table), db.getRowCount(table)]);
325
366
  if (!info.success || !info.data || info.data.columns.length === 0) {
@@ -330,6 +371,7 @@ function registerPages(router, ctx) {
330
371
  res.locals.currentTable = table;
331
372
  res.render('pages/seed', {
332
373
  title: `Seed data · ${table}`,
374
+ dbId,
333
375
  table,
334
376
  allTables,
335
377
  mode: initialMode,
@@ -338,16 +380,22 @@ function registerPages(router, ctx) {
338
380
  colCount: info.data.columns.length,
339
381
  maxRows: index_1.MAX_SEED_ROWS,
340
382
  quickCounts: [10, 50, 100, 500, 1000, index_1.MAX_SEED_ROWS],
341
- seedConfig: { table, columns, rowCount, maxRows: index_1.MAX_SEED_ROWS, mode: initialMode },
383
+ seedConfig: { dbId, table, columns, rowCount, maxRows: index_1.MAX_SEED_ROWS, mode: initialMode },
342
384
  });
343
385
  }
344
386
  catch (err) {
345
387
  next(err);
346
388
  }
347
389
  });
348
- router.get('/tables/:table/rows/new', async (req, res, next) => {
390
+ router.get('/tables/:db/:table/rows/new', async (req, res, next) => {
349
391
  try {
392
+ const db = ctx.getDb(req);
393
+ const dbId = req.params.db;
350
394
  const table = req.params.table;
395
+ res.locals.dbId = dbId;
396
+ const all = await db.listTables();
397
+ const allNames = (all.data ?? []).map((t) => t.name);
398
+ initPageLocals(res, db, dbId, allNames);
351
399
  const info = await db.getTableInfo(table);
352
400
  if (!info.success || !info.data || info.data.columns.length === 0) {
353
401
  return notFound(res, `Table "${table}" does not exist.`);
@@ -356,21 +404,28 @@ function registerPages(router, ctx) {
356
404
  res.locals.currentTable = table;
357
405
  res.render('pages/form', {
358
406
  title: `New row · ${table}`,
407
+ dbId,
359
408
  table,
360
409
  mode: 'insert',
361
410
  pk: null,
362
411
  pkColumns,
363
412
  infoSummary: `${info.data.columns.length} column(s)` + (pkColumns.length ? ` · PK: ${pkColumns.join(', ')}` : ''),
364
- config: { table, mode: 'insert', pk: null, pkColumns },
413
+ config: { dbId, table, mode: 'insert', pk: null, pkColumns },
365
414
  });
366
415
  }
367
416
  catch (err) {
368
417
  next(err);
369
418
  }
370
419
  });
371
- router.get('/tables/:table/rows/:id/edit', async (req, res, next) => {
420
+ router.get('/tables/:db/:table/rows/:id/edit', async (req, res, next) => {
372
421
  try {
422
+ const db = ctx.getDb(req);
423
+ const dbId = req.params.db;
373
424
  const table = req.params.table;
425
+ res.locals.dbId = dbId;
426
+ const all = await db.listTables();
427
+ const allNames = (all.data ?? []).map((t) => t.name);
428
+ initPageLocals(res, db, dbId, allNames);
374
429
  const info = await db.getTableInfo(table);
375
430
  if (!info.success || !info.data || info.data.columns.length === 0) {
376
431
  return notFound(res, `Table "${table}" does not exist.`);
@@ -383,57 +438,96 @@ function registerPages(router, ctx) {
383
438
  res.locals.currentTable = table;
384
439
  res.render('pages/form', {
385
440
  title: `Edit row · ${table}`,
441
+ dbId,
386
442
  table,
387
443
  mode: 'edit',
388
444
  pk: req.params.id,
389
445
  pkColumns,
390
446
  infoSummary: `${info.data.columns.length} column(s)` + (info.data.primaryKey.length ? ` · PK: ${info.data.primaryKey.join(', ')}` : ''),
391
- config: { table, mode: 'edit', pk: req.params.id, pkColumns },
447
+ config: { dbId, table, mode: 'edit', pk: req.params.id, pkColumns },
392
448
  });
393
449
  }
394
450
  catch (err) {
395
451
  next(err);
396
452
  }
397
453
  });
398
- router.get('/erd', (_req, res) => {
399
- res.render('pages/erd', { title: 'ER Diagram' });
454
+ router.get('/erd/:db', async (req, res, next) => {
455
+ try {
456
+ const db = ctx.getDb(req);
457
+ const dbId = req.params.db;
458
+ res.locals.dbId = dbId;
459
+ const all = await db.listTables();
460
+ const allNames = (all.data ?? []).map((t) => t.name);
461
+ initPageLocals(res, db, dbId, allNames);
462
+ res.render('pages/erd', { title: 'ER Diagram', dbId });
463
+ }
464
+ catch (err) {
465
+ next(err);
466
+ }
400
467
  });
401
- router.get('/designer', (_req, res) => {
402
- res.render('pages/designer', {
403
- title: 'New table',
404
- dialect: db.dialect,
405
- isSqlite: db.dialect === 'sqlite',
406
- isPostgres: db.dialect === 'postgres',
407
- });
468
+ router.get('/designer/:db', async (req, res, next) => {
469
+ try {
470
+ const db = ctx.getDb(req);
471
+ const dbId = req.params.db;
472
+ res.locals.dbId = dbId;
473
+ const all = await db.listTables();
474
+ const allNames = (all.data ?? []).map((t) => t.name);
475
+ initPageLocals(res, db, dbId, allNames);
476
+ res.render('pages/designer', {
477
+ title: 'New table',
478
+ dbId,
479
+ dialect: db.dialect,
480
+ isSqlite: db.dialect === 'sqlite',
481
+ isPostgres: db.dialect === 'postgres',
482
+ });
483
+ }
484
+ catch (err) {
485
+ next(err);
486
+ }
408
487
  });
409
- router.get('/query', async (_req, res, next) => {
488
+ router.get('/query/:db', async (req, res, next) => {
410
489
  try {
490
+ const db = ctx.getDb(req);
491
+ const dbId = req.params.db;
492
+ res.locals.dbId = dbId;
493
+ const all = await db.listTables();
494
+ const allNames = (all.data ?? []).map((t) => t.name);
495
+ initPageLocals(res, db, dbId, allNames);
411
496
  const queries = await db.listSavedQueries();
412
- res.render('pages/query', { title: 'Query editor', savedQueries: queries.success ? queries.data : [] });
497
+ res.render('pages/query', { title: 'Query editor', dbId, savedQueries: queries.success ? queries.data : [] });
413
498
  }
414
499
  catch (err) {
415
500
  next(err);
416
501
  }
417
502
  });
418
- router.get('/export', async (_req, res, next) => {
503
+ router.get('/export/:db', async (req, res, next) => {
419
504
  try {
505
+ const db = ctx.getDb(req);
506
+ const dbId = req.params.db;
420
507
  const dump = await (0, export_1.generateSqlDump)(db);
421
508
  if (!dump.success || !dump.data)
422
509
  throw new Error(dump.error ?? 'Failed to generate dump.');
423
510
  const stamp = new Date().toISOString().replace(/[:.]/g, '-');
424
511
  res.setHeader('Content-Type', 'application/sql; charset=utf-8');
425
- res.setHeader('Content-Disposition', `attachment; filename="admindb-${stamp}.sql"`);
512
+ res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(dbId)}-${stamp}.sql"`);
426
513
  res.send(dump.data);
427
514
  }
428
515
  catch (err) {
429
516
  next(err);
430
517
  }
431
518
  });
432
- router.get('/info', async (_req, res, next) => {
519
+ router.get('/info/:db', async (req, res, next) => {
433
520
  try {
521
+ const db = ctx.getDb(req);
522
+ const dbId = req.params.db;
523
+ res.locals.dbId = dbId;
524
+ const all = await db.listTables();
525
+ const allNames = (all.data ?? []).map((t) => t.name);
526
+ initPageLocals(res, db, dbId, allNames);
434
527
  const settingsRes = await db.getSettings();
435
528
  res.render('pages/info', {
436
529
  title: 'Database Info',
530
+ dbId,
437
531
  settings: settingsRes.success ? settingsRes.data : {},
438
532
  dialect: db.dialect,
439
533
  dbPath: db.path,
@@ -19,30 +19,42 @@ function isServerlessEnvironment(env = process.env) {
19
19
  env.CF_PAGES);
20
20
  }
21
21
  function createServerlessHandler(options = {}) {
22
- const rawTarget = options.connection || options.dbPath || '';
22
+ const envTarget = process.env.ADMINDB_CONNECTION ||
23
+ process.env.DATABASE_URL ||
24
+ process.env.POSTGRES_URL ||
25
+ process.env.PG_CONNECTION ||
26
+ '';
27
+ const rawTarget = options.connection || options.dbPath || ((0, common_1.isPostgresConnectionString)(envTarget) ? envTarget : '');
23
28
  const isPg = Boolean(options.pgOptions) ||
24
29
  (0, common_1.isPostgresConnectionString)(rawTarget) ||
25
30
  options.db?.dialect === 'postgres';
26
31
  const defaultReadonly = !isPg;
27
32
  const app = (0, app_1.createRouter)({
28
33
  ...options,
34
+ connection: options.connection || ((0, common_1.isPostgresConnectionString)(rawTarget) ? rawTarget : undefined),
29
35
  serverless: options.serverless ?? true,
30
- readonly: options.readonly ?? defaultReadonly,
36
+ readonly: options.readonly !== undefined ? Boolean(options.readonly) : defaultReadonly,
31
37
  });
32
38
  return (req, res) => {
33
39
  app(req, res);
34
40
  };
35
41
  }
36
42
  function createLambdaHandler(options = {}) {
37
- const rawTarget = options.connection || options.dbPath || '';
43
+ const envTarget = process.env.ADMINDB_CONNECTION ||
44
+ process.env.DATABASE_URL ||
45
+ process.env.POSTGRES_URL ||
46
+ process.env.PG_CONNECTION ||
47
+ '';
48
+ const rawTarget = options.connection || options.dbPath || ((0, common_1.isPostgresConnectionString)(envTarget) ? envTarget : '');
38
49
  const isPg = Boolean(options.pgOptions) ||
39
50
  (0, common_1.isPostgresConnectionString)(rawTarget) ||
40
51
  options.db?.dialect === 'postgres';
41
52
  const defaultReadonly = !isPg;
42
53
  const app = (0, app_1.createRouter)({
43
54
  ...options,
55
+ connection: options.connection || ((0, common_1.isPostgresConnectionString)(rawTarget) ? rawTarget : undefined),
44
56
  serverless: options.serverless ?? true,
45
- readonly: options.readonly ?? defaultReadonly,
57
+ readonly: options.readonly !== undefined ? Boolean(options.readonly) : defaultReadonly,
46
58
  });
47
59
  return async (event, _context) => {
48
60
  return new Promise((resolve) => {