admindb 2.4.0 → 2.5.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 (135) hide show
  1. package/LICENSE +20 -20
  2. package/dist/app.js +36 -25
  3. package/dist/core/context.d.ts +1 -0
  4. package/dist/core/context.js +2 -0
  5. package/dist/core/errors.d.ts +1 -0
  6. package/dist/core/errors.js +39 -0
  7. package/dist/core/index.d.ts +1 -0
  8. package/dist/core/index.js +20 -0
  9. package/dist/core/result.d.ts +1 -0
  10. package/dist/core/result.js +10 -0
  11. package/dist/core/router.d.ts +1 -0
  12. package/dist/core/router.js +37 -0
  13. package/dist/data/chain.js +1 -1
  14. package/dist/data/engine.js +1 -1
  15. package/dist/db/database.d.ts +1 -1
  16. package/dist/db/database.js +9 -31
  17. package/dist/db/dialects/index.d.ts +1 -0
  18. package/dist/db/dialects/index.js +58 -0
  19. package/dist/db/dialects/postgres/ddl.d.ts +1 -0
  20. package/dist/db/dialects/postgres/ddl.js +107 -0
  21. package/dist/db/dialects/postgres/dialect.d.ts +1 -0
  22. package/dist/db/dialects/postgres/dialect.js +28 -0
  23. package/dist/db/dialects/postgres/introspector.d.ts +1 -0
  24. package/dist/db/dialects/postgres/introspector.js +265 -0
  25. package/dist/db/dialects/postgres/types.d.ts +1 -0
  26. package/dist/db/dialects/postgres/types.js +83 -0
  27. package/dist/db/dialects/sqlite/ddl.d.ts +1 -0
  28. package/dist/db/dialects/sqlite/ddl.js +244 -0
  29. package/dist/db/dialects/sqlite/dialect.d.ts +1 -0
  30. package/dist/db/dialects/sqlite/dialect.js +25 -0
  31. package/dist/db/dialects/sqlite/introspector.d.ts +1 -0
  32. package/dist/db/dialects/sqlite/introspector.js +214 -0
  33. package/dist/db/dialects/sqlite/types.d.ts +1 -0
  34. package/dist/db/dialects/sqlite/types.js +69 -0
  35. package/dist/db/dialects/types.d.ts +1 -0
  36. package/dist/db/dialects/types.js +15 -0
  37. package/dist/db/export.js +1 -1
  38. package/dist/db/index.d.ts +1 -1
  39. package/dist/db/index.js +1 -1
  40. package/dist/db/manager.d.ts +1 -1
  41. package/dist/db/manager.js +101 -21
  42. package/dist/db/postgres.d.ts +1 -1
  43. package/dist/db/postgres.js +20 -284
  44. package/dist/db/types.d.ts +1 -1
  45. package/dist/db/types.js +0 -4
  46. package/dist/index.d.ts +1 -1
  47. package/dist/index.js +4 -1
  48. package/dist/modules/databases/databases.routes.d.ts +1 -0
  49. package/dist/modules/databases/databases.routes.js +150 -0
  50. package/dist/modules/databases/databases.service.d.ts +1 -0
  51. package/dist/modules/databases/databases.service.js +134 -0
  52. package/dist/modules/databases/index.d.ts +1 -0
  53. package/dist/{routes → modules/databases}/index.js +2 -3
  54. package/dist/modules/erd/erd.routes.d.ts +1 -0
  55. package/dist/modules/erd/erd.routes.js +32 -0
  56. package/dist/modules/erd/erd.service.d.ts +1 -0
  57. package/dist/modules/erd/erd.service.js +39 -0
  58. package/dist/modules/erd/index.d.ts +1 -0
  59. package/dist/modules/erd/index.js +18 -0
  60. package/dist/modules/index.d.ts +1 -0
  61. package/dist/modules/index.js +62 -0
  62. package/dist/modules/query/index.d.ts +1 -0
  63. package/dist/modules/query/index.js +18 -0
  64. package/dist/modules/query/query.routes.d.ts +1 -0
  65. package/dist/modules/query/query.routes.js +54 -0
  66. package/dist/modules/query/query.service.d.ts +1 -0
  67. package/dist/modules/query/query.service.js +95 -0
  68. package/dist/modules/rows/blob.helper.d.ts +1 -0
  69. package/dist/modules/rows/blob.helper.js +40 -0
  70. package/dist/modules/rows/display.helper.d.ts +1 -0
  71. package/dist/modules/rows/display.helper.js +73 -0
  72. package/dist/modules/rows/index.d.ts +1 -0
  73. package/dist/modules/rows/index.js +20 -0
  74. package/dist/modules/rows/rows.routes.d.ts +1 -0
  75. package/dist/modules/rows/rows.routes.js +555 -0
  76. package/dist/modules/rows/rows.service.d.ts +1 -0
  77. package/dist/modules/rows/rows.service.js +433 -0
  78. package/dist/modules/schema/index.d.ts +1 -0
  79. package/dist/modules/schema/index.js +18 -0
  80. package/dist/modules/schema/schema.routes.d.ts +1 -0
  81. package/dist/modules/schema/schema.routes.js +171 -0
  82. package/dist/modules/schema/schema.service.d.ts +1 -0
  83. package/dist/modules/schema/schema.service.js +58 -0
  84. package/dist/modules/seed/index.d.ts +1 -0
  85. package/dist/modules/seed/index.js +18 -0
  86. package/dist/modules/seed/seed.routes.d.ts +1 -0
  87. package/dist/modules/seed/seed.routes.js +212 -0
  88. package/dist/modules/seed/seed.service.d.ts +1 -0
  89. package/dist/{routes/api/helpers.js → modules/seed/seed.service.js} +86 -109
  90. package/dist/modules/tables/index.d.ts +1 -0
  91. package/dist/modules/tables/index.js +18 -0
  92. package/dist/modules/tables/tables.routes.d.ts +1 -0
  93. package/dist/modules/tables/tables.routes.js +194 -0
  94. package/dist/modules/tables/tables.service.d.ts +1 -0
  95. package/dist/modules/tables/tables.service.js +218 -0
  96. package/dist/public/js/browse.js +1 -1
  97. package/dist/public/js/query.js +1 -1
  98. package/dist/types/api.d.ts +1 -1
  99. package/dist/utils/common.d.ts +1 -1
  100. package/dist/utils/common.js +1 -3
  101. package/dist/utils/stream.d.ts +1 -0
  102. package/dist/utils/stream.js +131 -0
  103. package/dist/views/pages/error.hbs +10 -1
  104. package/dist/views/pages/home.hbs +1 -1
  105. package/dist/views/pages/query.hbs +1 -1
  106. package/dist/views/pages/seed-select.hbs +1 -252
  107. package/dist/views/pages/seed.hbs +1 -307
  108. package/dist/views/pages/table.hbs +1 -1
  109. package/dist/views/partials/sidebar.hbs +1 -1
  110. package/docs/API.md +4 -17
  111. package/docs/EXAMPLES.md +579 -579
  112. package/docs/SECURITY.md +265 -265
  113. package/package.json +4 -2
  114. package/dist/db/introspection.d.ts +0 -1
  115. package/dist/db/introspection.js +0 -164
  116. package/dist/routes/api/erd.d.ts +0 -1
  117. package/dist/routes/api/erd.js +0 -45
  118. package/dist/routes/api/helpers.d.ts +0 -1
  119. package/dist/routes/api/import-export.d.ts +0 -1
  120. package/dist/routes/api/import-export.js +0 -84
  121. package/dist/routes/api/index.d.ts +0 -1
  122. package/dist/routes/api/index.js +0 -55
  123. package/dist/routes/api/query.d.ts +0 -1
  124. package/dist/routes/api/query.js +0 -77
  125. package/dist/routes/api/rows.d.ts +0 -1
  126. package/dist/routes/api/rows.js +0 -373
  127. package/dist/routes/api/seed.d.ts +0 -1
  128. package/dist/routes/api/seed.js +0 -180
  129. package/dist/routes/api/tables.d.ts +0 -1
  130. package/dist/routes/api/tables.js +0 -230
  131. package/dist/routes/databases.d.ts +0 -1
  132. package/dist/routes/databases.js +0 -212
  133. package/dist/routes/index.d.ts +0 -1
  134. package/dist/routes/pages.d.ts +0 -1
  135. package/dist/routes/pages.js +0 -540
@@ -4,8 +4,8 @@ exports.PostgresDatabase = void 0;
4
4
  const pg_1 = require("pg");
5
5
  const generator_1 = require("../sql/generator");
6
6
  const common_1 = require("../utils/common");
7
- const types_1 = require("./types");
8
7
  const filters_1 = require("./filters");
8
+ const index_1 = require("./dialects/index");
9
9
  class PostgresDatabase {
10
10
  pool;
11
11
  logger;
@@ -13,6 +13,7 @@ class PostgresDatabase {
13
13
  isReadOnly;
14
14
  dialect = 'postgres';
15
15
  schema;
16
+ dialectInstance;
16
17
  constructor(connection, logger, options = {}) {
17
18
  this.logger = logger;
18
19
  const opts = typeof connection === 'string'
@@ -20,6 +21,7 @@ class PostgresDatabase {
20
21
  : { ...connection, ...options };
21
22
  this.isReadOnly = Boolean(opts.readonly);
22
23
  this.schema = opts.schema || 'public';
24
+ this.dialectInstance = (0, index_1.getDialect)('postgres', this.schema);
23
25
  let sslConfig = opts.ssl;
24
26
  if (opts.connectionString) {
25
27
  try {
@@ -62,11 +64,6 @@ class PostgresDatabase {
62
64
  this.pool.on('error', (err) => {
63
65
  this.logger.error(`Unexpected PostgreSQL client error: ${(0, common_1.errorMessage)(err)}`);
64
66
  });
65
- if (!this.isReadOnly) {
66
- this.initSchema().catch((err) => {
67
- this.logger.warn(`Failed to initialize Postgres schema: ${(0, common_1.errorMessage)(err)}`);
68
- });
69
- }
70
67
  this.logger.info(`Initialized PostgreSQL pool for ${this.path}${this.isReadOnly ? ' (read-only)' : ''}`);
71
68
  }
72
69
  readonlyBlocked() {
@@ -113,16 +110,6 @@ class PostgresDatabase {
113
110
  client.release();
114
111
  }
115
112
  }
116
- async initSchema() {
117
- await this.pool.query(`
118
- CREATE TABLE IF NOT EXISTS ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} (
119
- id SERIAL PRIMARY KEY,
120
- name VARCHAR(255) NOT NULL UNIQUE,
121
- sql TEXT NOT NULL,
122
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
123
- );
124
- `);
125
- }
126
113
  hasMultipleStatements(sql) {
127
114
  const s = String(sql ?? '').trim();
128
115
  if (!s)
@@ -173,121 +160,10 @@ class PostgresDatabase {
173
160
  return this.execResult(sql);
174
161
  }
175
162
  async listTables() {
176
- return this.tryRun(async () => {
177
- const res = await this.pool.query(`SELECT table_name AS name
178
- FROM information_schema.tables
179
- WHERE table_schema = $1 AND table_type = 'BASE TABLE'
180
- ORDER BY table_name;`, [this.schema]);
181
- return res.rows.map((r) => ({ name: r.name }));
182
- });
163
+ return this.dialectInstance.introspector.listTables(this.pool);
183
164
  }
184
165
  async getTableInfo(table) {
185
- return this.tryRun(async () => {
186
- const [colRes, pkRes, fkRes, idxRes] = await Promise.all([
187
- this.pool.query(`SELECT
188
- ordinal_position AS cid,
189
- column_name AS name,
190
- data_type AS type,
191
- udt_name,
192
- CASE WHEN is_nullable = 'NO' THEN 1 ELSE 0 END AS notnull,
193
- column_default AS dflt_value
194
- FROM information_schema.columns
195
- WHERE table_schema = $1 AND table_name = $2
196
- ORDER BY ordinal_position;`, [this.schema, table]),
197
- this.pool.query(`SELECT kcu.column_name
198
- FROM information_schema.table_constraints tc
199
- JOIN information_schema.key_column_usage kcu
200
- ON tc.constraint_name = kcu.constraint_name
201
- AND tc.table_schema = kcu.table_schema
202
- WHERE tc.constraint_type = 'PRIMARY KEY'
203
- AND tc.table_schema = $1
204
- AND tc.table_name = $2
205
- ORDER BY kcu.ordinal_position;`, [this.schema, table]),
206
- this.pool.query(`SELECT
207
- row_number() OVER () AS id,
208
- kcu.position_in_unique_constraint AS seq,
209
- ccu.table_name AS table,
210
- kcu.column_name AS "from",
211
- ccu.column_name AS "to",
212
- rc.update_rule AS on_update,
213
- rc.delete_rule AS on_delete
214
- FROM information_schema.table_constraints AS tc
215
- JOIN information_schema.key_column_usage AS kcu
216
- ON tc.constraint_name = kcu.constraint_name
217
- AND tc.table_schema = kcu.table_schema
218
- JOIN information_schema.constraint_column_usage AS ccu
219
- ON ccu.constraint_name = tc.constraint_name
220
- AND ccu.table_schema = tc.table_schema
221
- JOIN information_schema.referential_constraints AS rc
222
- ON rc.constraint_name = tc.constraint_name
223
- AND rc.constraint_schema = tc.table_schema
224
- WHERE tc.constraint_type = 'FOREIGN KEY'
225
- AND tc.table_schema = $1
226
- AND tc.table_name = $2;`, [this.schema, table]),
227
- this.pool.query(`SELECT
228
- i.relname AS name,
229
- ix.indisunique AS unique,
230
- pg_get_indexdef(ix.indexrelid) AS sql,
231
- ARRAY(
232
- SELECT pg_get_indexdef(ix.indexrelid, k + 1, true)
233
- FROM generate_subscripts(ix.indkey, 1) as k
234
- ORDER BY k
235
- ) AS columns
236
- FROM pg_index ix
237
- JOIN pg_class t ON t.oid = ix.indrelid
238
- JOIN pg_class i ON i.oid = ix.indexrelid
239
- JOIN pg_namespace n ON n.oid = t.relnamespace
240
- WHERE n.nspname = $1 AND t.relname = $2;`, [this.schema, table]),
241
- ]);
242
- const primaryKey = pkRes.rows.map((r) => r.column_name);
243
- const columns = colRes.rows.map((c) => {
244
- const isPk = primaryKey.includes(c.name);
245
- const displayType = c.type === 'USER-DEFINED' ? c.udt_name : c.type;
246
- return {
247
- cid: c.cid,
248
- name: c.name,
249
- type: displayType.toUpperCase(),
250
- notnull: c.notnull,
251
- dflt_value: c.dflt_value,
252
- pk: isPk ? primaryKey.indexOf(c.name) + 1 : 0,
253
- };
254
- });
255
- const indexes = idxRes.rows.map((ix) => ({
256
- name: ix.name,
257
- unique: Boolean(ix.unique),
258
- partial: 0,
259
- origin: 'c',
260
- columns: Array.isArray(ix.columns) ? ix.columns : [],
261
- sql: ix.sql,
262
- }));
263
- const colDefs = columns.map((c) => {
264
- let def = ` ${(0, generator_1.quoteIdentifier)(c.name)} ${c.type}`;
265
- if (c.notnull)
266
- def += ' NOT NULL';
267
- if (c.dflt_value)
268
- def += ` DEFAULT ${c.dflt_value}`;
269
- return def;
270
- });
271
- if (primaryKey.length) {
272
- colDefs.push(` PRIMARY KEY (${primaryKey.map(generator_1.quoteIdentifier).join(', ')})`);
273
- }
274
- return {
275
- table,
276
- columns,
277
- foreignKeys: fkRes.rows.map((f) => ({
278
- id: Number(f.id),
279
- seq: Number(f.seq ?? 1),
280
- table: f.table,
281
- from: f.from,
282
- to: f.to,
283
- on_update: f.on_update,
284
- on_delete: f.on_delete,
285
- })),
286
- primaryKey,
287
- indexes,
288
- sql: `CREATE TABLE ${(0, generator_1.quoteIdentifier)(table)} (\n${colDefs.join(',\n')}\n);`,
289
- };
290
- });
166
+ return this.dialectInstance.introspector.getTableInfo(this.pool, table);
291
167
  }
292
168
  async getRowCount(table, filters) {
293
169
  return this.tryRun(async () => {
@@ -450,24 +326,6 @@ class PostgresDatabase {
450
326
  return { success: false, error: info.error };
451
327
  return { success: true, data: info.data.sql };
452
328
  }
453
- async listSavedQueries() {
454
- return this.tryRun(async () => {
455
- const res = await this.pool.query(`SELECT id, name, sql, created_at FROM ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} ORDER BY name`);
456
- return res.rows;
457
- });
458
- }
459
- async saveQuery(name, sql) {
460
- return this.tryRun(async () => {
461
- const res = await this.pool.query(`INSERT INTO ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} (name, sql) VALUES ($1, $2) RETURNING id`, [name, sql]);
462
- return { changes: res.rowCount ?? 1, lastInsertRowid: Number(res.rows[0]?.id ?? null) };
463
- });
464
- }
465
- async deleteSavedQuery(id) {
466
- return this.tryRun(async () => {
467
- const res = await this.pool.query(`DELETE FROM ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} WHERE id = $1`, [Number(id)]);
468
- return { changes: res.rowCount ?? 0, lastInsertRowid: null };
469
- });
470
- }
471
329
  async getSettings() {
472
330
  return this.tryRun(async () => {
473
331
  const keys = ['server_version', 'max_connections', 'port', 'timezone', 'shared_buffers', 'work_mem'];
@@ -488,100 +346,16 @@ class PostgresDatabase {
488
346
  });
489
347
  }
490
348
  async getSchema(table) {
491
- return this.tryRun(async () => {
492
- const infoR = await this.getTableInfo(table);
493
- if (!infoR.success || !infoR.data)
494
- throw new Error(infoR.error || `Table ${table} not found.`);
495
- const { columns, foreignKeys, indexes, primaryKey } = infoR.data;
496
- const refRes = await this.pool.query(`SELECT
497
- tc.table_name AS table,
498
- kcu.column_name AS "from",
499
- ccu.column_name AS "to"
500
- FROM information_schema.table_constraints AS tc
501
- JOIN information_schema.key_column_usage AS kcu
502
- ON tc.constraint_name = kcu.constraint_name
503
- AND tc.table_schema = kcu.table_schema
504
- JOIN information_schema.constraint_column_usage AS ccu
505
- ON ccu.constraint_name = tc.constraint_name
506
- AND ccu.table_schema = tc.table_schema
507
- WHERE tc.constraint_type = 'FOREIGN KEY'
508
- AND tc.table_schema = $1
509
- AND ccu.table_name = $2;`, [this.schema, table]);
510
- const references = refRes.rows;
511
- const columnDetails = columns.map((col) => {
512
- const isPk = primaryKey.includes(col.name);
513
- const fk = foreignKeys.find((f) => f.from === col.name) ?? null;
514
- const matchingIndexes = indexes.filter((i) => i.columns.includes(col.name));
515
- const unique = matchingIndexes.some((i) => i.unique && i.columns.length === 1 && i.columns[0] === col.name);
516
- const indexed = matchingIndexes.length > 0;
517
- const referencedBy = references
518
- .filter((r) => r.to === col.name)
519
- .map((r) => ({ table: r.table, from: r.from }));
520
- const dropBlockers = [];
521
- if (isPk && primaryKey.length === 1)
522
- dropBlockers.push('Primary key');
523
- if (referencedBy.length > 0) {
524
- dropBlockers.push(`Referenced by ${referencedBy.map((r) => `${r.table}.${r.from}`).join(', ')}`);
525
- }
526
- return {
527
- ...col,
528
- unique,
529
- indexed,
530
- referencedBy,
531
- fk,
532
- canDrop: dropBlockers.length === 0,
533
- dropBlockers,
534
- };
535
- });
536
- return {
537
- table,
538
- columns: columnDetails,
539
- foreignKeys,
540
- indexes,
541
- references,
542
- };
543
- });
349
+ return this.dialectInstance.introspector.getSchema(this.pool, table);
544
350
  }
545
351
  async getReferencingTables(table) {
546
- return this.tryRun(async () => {
547
- const refRes = await this.pool.query(`SELECT
548
- tc.table_name AS table,
549
- kcu.column_name AS "from",
550
- ccu.column_name AS "to"
551
- FROM information_schema.table_constraints AS tc
552
- JOIN information_schema.key_column_usage AS kcu
553
- ON tc.constraint_name = kcu.constraint_name
554
- AND tc.table_schema = kcu.table_schema
555
- JOIN information_schema.constraint_column_usage AS ccu
556
- ON ccu.constraint_name = tc.constraint_name
557
- AND ccu.table_schema = tc.table_schema
558
- WHERE tc.constraint_type = 'FOREIGN KEY'
559
- AND tc.table_schema = $1
560
- AND ccu.table_name = $2;`, [this.schema, table]);
561
- const byTable = new Map();
562
- for (const r of refRes.rows) {
563
- if (!byTable.has(r.table))
564
- byTable.set(r.table, []);
565
- byTable.get(r.table).push({ from: r.from, to: r.to });
566
- }
567
- const results = [];
568
- for (const [tbl, refs] of byTable.entries()) {
569
- try {
570
- const countRes = await this.pool.query(`SELECT COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(tbl)} WHERE ${refs.map((r) => `${(0, generator_1.quoteIdentifier)(r.from)} IS NOT NULL`).join(' OR ')}`);
571
- results.push({ table: tbl, refs, refCount: Number(countRes.rows[0]?.c ?? 0) });
572
- }
573
- catch {
574
- results.push({ table: tbl, refs, refCount: 0 });
575
- }
576
- }
577
- return results;
578
- });
352
+ return this.dialectInstance.introspector.getReferencingTables(this.pool, table);
579
353
  }
580
354
  async renameTable(oldName, newName) {
581
355
  if (this.isReadOnly)
582
356
  return this.readonlyBlocked();
583
357
  return this.tryRun(async () => {
584
- await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(oldName)} RENAME TO ${(0, generator_1.quoteIdentifier)(newName)};`);
358
+ await this.pool.query(this.dialectInstance.ddl.renameTable(oldName, newName));
585
359
  return { changes: 1 };
586
360
  });
587
361
  }
@@ -589,19 +363,13 @@ class PostgresDatabase {
589
363
  if (this.isReadOnly)
590
364
  return this.readonlyBlocked();
591
365
  return this.tryRun(async () => {
592
- let sql = `ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ADD COLUMN ${(0, generator_1.quoteIdentifier)(col.name)} ${col.type}`;
593
- if (col.notNull)
594
- sql += ' NOT NULL';
595
- if (col.defaultValue !== undefined && col.defaultValue !== null && col.defaultValue !== '') {
596
- sql += ` DEFAULT ${col.defaultValue}`;
597
- }
598
- if (col.foreignKey) {
599
- sql += ` REFERENCES ${(0, generator_1.quoteIdentifier)(col.foreignKey.table)} (${(0, generator_1.quoteIdentifier)(col.foreignKey.column)})`;
600
- }
601
- await this.pool.query(sql);
366
+ await this.pool.query(this.dialectInstance.ddl.addColumn(table, col));
602
367
  if (col.unique) {
603
- const idxName = `idx_${table}_${col.name}_unique`;
604
- await this.pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS ${(0, generator_1.quoteIdentifier)(idxName)} ON ${(0, generator_1.quoteIdentifier)(table)} (${(0, generator_1.quoteIdentifier)(col.name)});`);
368
+ await this.pool.query(this.dialectInstance.ddl.createIndex(table, {
369
+ name: `idx_${table}_${col.name}_unique`,
370
+ columns: [col.name],
371
+ unique: true,
372
+ }));
605
373
  }
606
374
  return { changes: 1 };
607
375
  });
@@ -609,42 +377,13 @@ class PostgresDatabase {
609
377
  async modifyColumn(table, oldColName, newColDef) {
610
378
  if (this.isReadOnly)
611
379
  return this.readonlyBlocked();
612
- return this.tryRun(async () => {
613
- const newName = String(newColDef.name || oldColName).trim();
614
- if (oldColName !== newName) {
615
- await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} RENAME COLUMN ${(0, generator_1.quoteIdentifier)(oldColName)} TO ${(0, generator_1.quoteIdentifier)(newName)};`);
616
- }
617
- const colToModify = newName;
618
- if (newColDef.type) {
619
- await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0, generator_1.quoteIdentifier)(colToModify)} TYPE ${newColDef.type} USING ${(0, generator_1.quoteIdentifier)(colToModify)}::${newColDef.type};`);
620
- }
621
- if (newColDef.notNull !== undefined) {
622
- const action = newColDef.notNull ? 'SET NOT NULL' : 'DROP NOT NULL';
623
- await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0, generator_1.quoteIdentifier)(colToModify)} ${action};`);
624
- }
625
- if (newColDef.defaultValue !== undefined) {
626
- const action = newColDef.defaultValue === null || newColDef.defaultValue === ''
627
- ? 'DROP DEFAULT'
628
- : `SET DEFAULT ${newColDef.defaultValue}`;
629
- await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0, generator_1.quoteIdentifier)(colToModify)} ${action};`);
630
- }
631
- if (newColDef.unique !== undefined) {
632
- const idxName = `idx_${table}_${colToModify}_unique`;
633
- if (newColDef.unique) {
634
- await this.pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS ${(0, generator_1.quoteIdentifier)(idxName)} ON ${(0, generator_1.quoteIdentifier)(table)} (${(0, generator_1.quoteIdentifier)(colToModify)});`);
635
- }
636
- else {
637
- await this.pool.query(`DROP INDEX IF EXISTS ${(0, generator_1.quoteIdentifier)(idxName)};`);
638
- }
639
- }
640
- return { changes: 1 };
641
- });
380
+ return this.dialectInstance.ddl.modifyColumn(this.pool, table, oldColName, newColDef);
642
381
  }
643
382
  async renameColumn(table, oldName, newName) {
644
383
  if (this.isReadOnly)
645
384
  return this.readonlyBlocked();
646
385
  return this.tryRun(async () => {
647
- await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} RENAME COLUMN ${(0, generator_1.quoteIdentifier)(oldName)} TO ${(0, generator_1.quoteIdentifier)(newName)};`);
386
+ await this.pool.query(this.dialectInstance.ddl.renameColumn(table, oldName, newName));
648
387
  return { changes: 1 };
649
388
  });
650
389
  }
@@ -652,7 +391,7 @@ class PostgresDatabase {
652
391
  if (this.isReadOnly)
653
392
  return this.readonlyBlocked();
654
393
  return this.tryRun(async () => {
655
- await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} DROP COLUMN ${(0, generator_1.quoteIdentifier)(column)} CASCADE;`);
394
+ await this.pool.query(this.dialectInstance.ddl.dropColumn(table, column));
656
395
  return { changes: 1 };
657
396
  });
658
397
  }
@@ -660,7 +399,7 @@ class PostgresDatabase {
660
399
  if (this.isReadOnly)
661
400
  return this.readonlyBlocked();
662
401
  return this.tryRun(async () => {
663
- await this.pool.query(`DROP TABLE ${(0, generator_1.quoteIdentifier)(table)} CASCADE;`);
402
+ await this.pool.query(this.dialectInstance.ddl.dropTable(table, { cascade: true }));
664
403
  return { changes: 1 };
665
404
  });
666
405
  }
@@ -668,10 +407,7 @@ class PostgresDatabase {
668
407
  if (this.isReadOnly)
669
408
  return this.readonlyBlocked();
670
409
  return this.tryRun(async () => {
671
- const unique = index.unique ? 'UNIQUE ' : '';
672
- const cols = index.columns.map(generator_1.quoteIdentifier).join(', ');
673
- const name = index.name || `idx_${table}_${index.columns.join('_')}`;
674
- await this.pool.query(`CREATE ${unique}INDEX IF NOT EXISTS ${(0, generator_1.quoteIdentifier)(name)} ON ${(0, generator_1.quoteIdentifier)(table)} (${cols});`);
410
+ await this.pool.query(this.dialectInstance.ddl.createIndex(table, index));
675
411
  return { changes: 1 };
676
412
  });
677
413
  }
@@ -679,7 +415,7 @@ class PostgresDatabase {
679
415
  if (this.isReadOnly)
680
416
  return this.readonlyBlocked();
681
417
  return this.tryRun(async () => {
682
- await this.pool.query(`DROP INDEX IF EXISTS ${(0, generator_1.quoteIdentifier)(indexName)} CASCADE;`);
418
+ await this.pool.query(this.dialectInstance.ddl.dropIndex(indexName));
683
419
  return { changes: 1 };
684
420
  });
685
421
  }
@@ -1 +1 @@
1
- import type {FilterValue} from '../utils/common';import type {ColumnDef,IndexDef} from '../sql/generator';export type {FilterCondition,FilterValue} from '../utils/common';export type DatabaseDialect='sqlite'|'postgres';export type SQLInputValue=null|number|bigint|string|boolean|Uint8Array|Buffer|Date;export interface Result<T=unknown>{success:boolean;data?:T;error?:string;}export interface MutationResult{changes?:number;lastInsertRowid?:number|null;}export interface TableListItem{name:string;}export interface ColumnInfo{cid:number;name:string;type:string;notnull:number;dflt_value:string|null;pk:number;}export interface ForeignKeyInfo{id:number;seq:number;table:string; from :string;to:string|null;on_update:string;on_delete:string;}export interface TableInfoData{table:string;columns:ColumnInfo[];foreignKeys:ForeignKeyInfo[];primaryKey:string[];indexes:IndexInfo[];sql:string|null;}export interface SavedQuery{id:number;name:string;sql:string;created_at:string;}export interface WhereClause{column:string;value:unknown;}export interface IndexInfo{name:string;unique:boolean;partial:number;origin:string;columns:string[];sql?:string|null;}export interface RowFilters{[column:string]:FilterValue;}export interface ColumnDetail extends ColumnInfo{unique:boolean;indexed:boolean;referencedBy:{table:string; from :string;}[];fk:ForeignKeyInfo|null;canDrop:boolean;dropBlockers:string[];}export interface SchemaInfo{table:string;columns:ColumnDetail[];foreignKeys:ForeignKeyInfo[];indexes:IndexInfo[];references:{table:string; from :string;to:string;}[];}export interface ReferencingTableInfo{table:string;refs:{ from :string;to:string;}[];refCount:number;}export interface DbOpenOptions{readonly ?:boolean;}export interface PostgresOptions extends DbOpenOptions{connectionString?:string;host?:string;port?:number;database?:string;user?:string;password?:string;ssl?:boolean|object;max?:number;schema?:string;}export interface QueryOptions{page?:number;limit?:number;orderBy?:string;orderDir?:'asc'|'desc';filters?:RowFilters;}export declare const INTERNAL_TABLES:{readonly savedQueries:"_saved_queries";};export interface IDatabase{readonly path:string;readonly isReadOnly:boolean;readonly dialect:DatabaseDialect;close():Promise<void>|void;all(sql:string,params?:SQLInputValue[]):Promise<Result<unknown[]>>;run(sql:string,params?:SQLInputValue[]):Promise<Result<MutationResult>>;execResult(sql:string):Promise<Result<{changes?:number;}>>;hasMultipleStatements(sql:string):boolean;runWrite(sql:string):Promise<Result<{changes?:number;}>>;listTables():Promise<Result<TableListItem[]>>;getTableInfo(table:string):Promise<Result<TableInfoData>>;getRowCount(table:string,filters?:RowFilters):Promise<Result<number>>;getRows(table:string,opts?:QueryOptions):Promise<Result<Record<string,unknown>[]>>;getAllRows(table:string):Promise<Result<Record<string,unknown>[]>>;getRow(table:string,where:WhereClause[]):Promise<Result<Record<string,unknown>|null>>;insertRow(table:string,fields:WhereClause[]):Promise<Result<MutationResult>>;insertRows(table:string,rows:WhereClause[][]):Promise<Result<{inserted:number;skipped:number;}>>;updateRow(table:string,fields:WhereClause[],where:WhereClause[]):Promise<Result<MutationResult>>;updateRows(table:string,rows:{fields:WhereClause[];where:WhereClause[];}[]):Promise<Result<{updated:number;}>>;deleteRow(table:string,where:WhereClause[]):Promise<Result<MutationResult>>;deleteRows(table:string,rows:WhereClause[][]):Promise<Result<{deleted:number;}>>;getRowsByPks(table:string,pks:WhereClause[][]):Promise<Result<Record<string,unknown>[]>>;getRowsByFk(table:string,column:string,value:unknown,limit?:number):Promise<Result<{rows:Record<string,unknown>[];total:number;}>>;getCreateStatement(table:string):Promise<Result<string|null>>;listSavedQueries():Promise<Result<SavedQuery[]>>;saveQuery(name:string,sql:string):Promise<Result<MutationResult>>;deleteSavedQuery(id:string|number):Promise<Result<MutationResult>>;getSettings():Promise<Result<Record<string,string>>>;getSchema(table:string):Promise<Result<SchemaInfo>>;getReferencingTables(table:string):Promise<Result<ReferencingTableInfo[]>>;renameTable(oldName:string,newName:string):Promise<Result<{changes?:number;}>>;addColumn(table:string,col:ColumnDef):Promise<Result<{changes?:number;}>>;modifyColumn(table:string,oldColName:string,newColDef:ColumnDef):Promise<Result<{changes?:number;}>>;renameColumn(table:string,oldName:string,newName:string):Promise<Result<{changes?:number;}>>;dropColumn(table:string,column:string):Promise<Result<{changes?:number;}>>;dropTable(table:string):Promise<Result<{changes?:number;}>>;createIndex(table:string,index:IndexDef):Promise<Result<{changes?:number;}>>;dropIndex(indexName:string):Promise<Result<{changes?:number;}>>;}
1
+ import type {FilterValue} from '../utils/common';import type {ColumnDef,IndexDef} from '../sql/generator';import type {IDatabaseDriver} from './dialects/types';export type {FilterCondition,FilterValue} from '../utils/common';export type {IDatabaseDriver} from './dialects/types';export type DatabaseDialect='sqlite'|'postgres';export type SQLInputValue=null|number|bigint|string|boolean|Uint8Array|Buffer|Date;export interface Result<T=unknown>{success:boolean;data?:T;error?:string;}export interface MutationResult{changes?:number;lastInsertRowid?:number|null;}export interface TableListItem{name:string;}export interface ColumnInfo{cid:number;name:string;type:string;notnull:number;dflt_value:string|null;pk:number;}export interface ForeignKeyInfo{id:number;seq:number;table:string; from :string;to:string|null;on_update:string;on_delete:string;}export interface TableInfoData{table:string;columns:ColumnInfo[];foreignKeys:ForeignKeyInfo[];primaryKey:string[];indexes:IndexInfo[];sql:string|null;}export interface WhereClause{column:string;value:unknown;}export interface IndexInfo{name:string;unique:boolean;partial:number;origin:string;columns:string[];sql?:string|null;}export interface RowFilters{[column:string]:FilterValue;}export interface ColumnDetail extends ColumnInfo{unique:boolean;indexed:boolean;referencedBy:{table:string; from :string;}[];fk:ForeignKeyInfo|null;canDrop:boolean;dropBlockers:string[];}export interface SchemaInfo{table:string;columns:ColumnDetail[];foreignKeys:ForeignKeyInfo[];indexes:IndexInfo[];references:{table:string; from :string;to:string;}[];}export interface ReferencingTableInfo{table:string;refs:{ from :string;to:string;}[];refCount:number;}export interface DbOpenOptions{readonly ?:boolean;}export interface PostgresOptions extends DbOpenOptions{connectionString?:string;host?:string;port?:number;database?:string;user?:string;password?:string;ssl?:boolean|object;max?:number;schema?:string;}export interface QueryOptions{page?:number;limit?:number;orderBy?:string;orderDir?:'asc'|'desc';filters?:RowFilters;}export interface IDatabase extends IDatabaseDriver{hasMultipleStatements(sql:string):boolean;runWrite(sql:string):Promise<Result<{changes?:number;}>>;listTables():Promise<Result<TableListItem[]>>;getTableInfo(table:string):Promise<Result<TableInfoData>>;getRowCount(table:string,filters?:RowFilters):Promise<Result<number>>;getRows(table:string,opts?:QueryOptions):Promise<Result<Record<string,unknown>[]>>;getAllRows(table:string):Promise<Result<Record<string,unknown>[]>>;getRow(table:string,where:WhereClause[]):Promise<Result<Record<string,unknown>|null>>;insertRow(table:string,fields:WhereClause[]):Promise<Result<MutationResult>>;insertRows(table:string,rows:WhereClause[][]):Promise<Result<{inserted:number;skipped:number;}>>;updateRow(table:string,fields:WhereClause[],where:WhereClause[]):Promise<Result<MutationResult>>;updateRows(table:string,rows:{fields:WhereClause[];where:WhereClause[];}[]):Promise<Result<{updated:number;}>>;deleteRow(table:string,where:WhereClause[]):Promise<Result<MutationResult>>;deleteRows(table:string,rows:WhereClause[][]):Promise<Result<{deleted:number;}>>;getRowsByPks(table:string,pks:WhereClause[][]):Promise<Result<Record<string,unknown>[]>>;getRowsByFk(table:string,column:string,value:unknown,limit?:number):Promise<Result<{rows:Record<string,unknown>[];total:number;}>>;getCreateStatement(table:string):Promise<Result<string|null>>;getSettings():Promise<Result<Record<string,string>>>;getSchema(table:string):Promise<Result<SchemaInfo>>;getReferencingTables(table:string):Promise<Result<ReferencingTableInfo[]>>;renameTable(oldName:string,newName:string):Promise<Result<{changes?:number;}>>;addColumn(table:string,col:ColumnDef):Promise<Result<{changes?:number;}>>;modifyColumn(table:string,oldColName:string,newColDef:ColumnDef):Promise<Result<{changes?:number;}>>;renameColumn(table:string,oldName:string,newName:string):Promise<Result<{changes?:number;}>>;dropColumn(table:string,column:string):Promise<Result<{changes?:number;}>>;dropTable(table:string):Promise<Result<{changes?:number;}>>;createIndex(table:string,index:IndexDef):Promise<Result<{changes?:number;}>>;dropIndex(indexName:string):Promise<Result<{changes?:number;}>>;}
package/dist/db/types.js CHANGED
@@ -1,6 +1,2 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.INTERNAL_TABLES = void 0;
4
- exports.INTERNAL_TABLES = {
5
- savedQueries: '_saved_queries',
6
- };
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export{createRouter,type AppOptions} from './app';export{createLogger,type Logger,type LogLevel} from './utils/logger';export{c,supportsColor} from './utils/colors';export{SqliteDatabase,PostgresDatabase,type IDatabase,type DatabaseDialect,type PostgresOptions,type MutationResult,type QueryOptions,type Result,type TableInfoData,type ColumnInfo,type ForeignKeyInfo,type WhereClause,type IndexInfo,type SchemaInfo,type ColumnDetail,type SavedQuery,type RowFilters,type FilterCondition,type FilterValue,type SQLInputValue} from './db/index';export{DbManager,type DbManagerOptions,type DatabaseEntry} from './db/manager';export{type ColumnDef,type IndexDef} from './sql/generator';export{type ColumnPlan,type ColumnGeneratorConfig,type GeneratorStrategyId,type StrategyDescriptor,type GenerateResult} from './data/index';export{type AuthConfig,type ResolvedAuthConfig,resolveAuthConfig,getOrCreatePersistentSecret,createSessionToken,verifySessionToken,parseCookies,constantTimeCompare,hashPassword,verifyPassword,DEFAULT_USERNAME,DEFAULT_PASSWORD_HASH,SESSION_COOKIE_NAME} from './auth/index';export{isServerlessEnvironment,createServerlessHandler,createLambdaHandler,type LambdaProxyResult} from './serverless';export{sniffMimeType,generateHexDump,analyzeBlob,isJsonString,formatJsonSafely,type MimeAnalysis,type HexDumpLine,type HexDumpResult,type BlobMetadata} from './utils/datatype';export * from './types/api';
1
+ export{createRouter,type AppOptions} from './app';export{createLogger,type Logger,type LogLevel} from './utils/logger';export{c,supportsColor} from './utils/colors';export{SqliteDatabase,PostgresDatabase,getDialect,type IDatabase,type IDialect,type ISchemaIntrospector,type IDdlGenerator,type DialectCapabilities,type DatabaseDialect,type PostgresOptions,type MutationResult,type QueryOptions,type Result,type TableInfoData,type ColumnInfo,type ForeignKeyInfo,type WhereClause,type IndexInfo,type SchemaInfo,type ColumnDetail,type RowFilters,type FilterCondition,type FilterValue,type SQLInputValue} from './db/index';export{DbManager,type DbManagerOptions,type DatabaseEntry} from './db/manager';export{type ColumnDef,type IndexDef} from './sql/generator';export{type ColumnPlan,type ColumnGeneratorConfig,type GeneratorStrategyId,type StrategyDescriptor,type GenerateResult} from './data/index';export{type AuthConfig,type ResolvedAuthConfig,resolveAuthConfig,getOrCreatePersistentSecret,createSessionToken,verifySessionToken,parseCookies,constantTimeCompare,hashPassword,verifyPassword,DEFAULT_USERNAME,DEFAULT_PASSWORD_HASH,SESSION_COOKIE_NAME} from './auth/index';export{isServerlessEnvironment,createServerlessHandler,createLambdaHandler,type LambdaProxyResult} from './serverless';export{sniffMimeType,generateHexDump,analyzeBlob,isJsonString,formatJsonSafely,type MimeAnalysis,type HexDumpLine,type HexDumpResult,type BlobMetadata} from './utils/datatype';export * from './core/index';export * from './modules/index';export * from './types/api';
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.formatJsonSafely = exports.isJsonString = exports.analyzeBlob = exports.generateHexDump = exports.sniffMimeType = exports.createLambdaHandler = exports.createServerlessHandler = exports.isServerlessEnvironment = exports.SESSION_COOKIE_NAME = exports.DEFAULT_PASSWORD_HASH = exports.DEFAULT_USERNAME = exports.verifyPassword = exports.hashPassword = exports.constantTimeCompare = exports.parseCookies = exports.verifySessionToken = exports.createSessionToken = exports.getOrCreatePersistentSecret = exports.resolveAuthConfig = exports.DbManager = exports.PostgresDatabase = exports.SqliteDatabase = exports.supportsColor = exports.c = exports.createLogger = exports.createRouter = void 0;
17
+ exports.formatJsonSafely = exports.isJsonString = exports.analyzeBlob = exports.generateHexDump = exports.sniffMimeType = exports.createLambdaHandler = exports.createServerlessHandler = exports.isServerlessEnvironment = exports.SESSION_COOKIE_NAME = exports.DEFAULT_PASSWORD_HASH = exports.DEFAULT_USERNAME = exports.verifyPassword = exports.hashPassword = exports.constantTimeCompare = exports.parseCookies = exports.verifySessionToken = exports.createSessionToken = exports.getOrCreatePersistentSecret = exports.resolveAuthConfig = exports.DbManager = exports.getDialect = exports.PostgresDatabase = exports.SqliteDatabase = exports.supportsColor = exports.c = exports.createLogger = exports.createRouter = void 0;
18
18
  var app_1 = require("./app");
19
19
  Object.defineProperty(exports, "createRouter", { enumerable: true, get: function () { return app_1.createRouter; } });
20
20
  var logger_1 = require("./utils/logger");
@@ -25,6 +25,7 @@ Object.defineProperty(exports, "supportsColor", { enumerable: true, get: functio
25
25
  var index_1 = require("./db/index");
26
26
  Object.defineProperty(exports, "SqliteDatabase", { enumerable: true, get: function () { return index_1.SqliteDatabase; } });
27
27
  Object.defineProperty(exports, "PostgresDatabase", { enumerable: true, get: function () { return index_1.PostgresDatabase; } });
28
+ Object.defineProperty(exports, "getDialect", { enumerable: true, get: function () { return index_1.getDialect; } });
28
29
  var manager_1 = require("./db/manager");
29
30
  Object.defineProperty(exports, "DbManager", { enumerable: true, get: function () { return manager_1.DbManager; } });
30
31
  var index_2 = require("./auth/index");
@@ -49,4 +50,6 @@ Object.defineProperty(exports, "generateHexDump", { enumerable: true, get: funct
49
50
  Object.defineProperty(exports, "analyzeBlob", { enumerable: true, get: function () { return datatype_1.analyzeBlob; } });
50
51
  Object.defineProperty(exports, "isJsonString", { enumerable: true, get: function () { return datatype_1.isJsonString; } });
51
52
  Object.defineProperty(exports, "formatJsonSafely", { enumerable: true, get: function () { return datatype_1.formatJsonSafely; } });
53
+ __exportStar(require("./core/index"), exports);
54
+ __exportStar(require("./modules/index"), exports);
52
55
  __exportStar(require("./types/api"), exports);
@@ -0,0 +1 @@
1
+ import type {Router} from 'express';import type {DbManager} from '../../db/manager';import type {Logger} from '../../utils/logger';import{type DatabaseRow,type FsEntry} from './databases.service';export type {DatabaseRow,FsEntry};export interface DatabasesRoutesContext{manager:DbManager;logger:Logger;basePath:string;readonly ?:boolean;allowBrowse?:boolean;browseRoot?:string;invalidate?:(id:string)=>void;}export declare function registerDatabasesRoutes(router:Router,ctx:DatabasesRoutesContext):void;
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerDatabasesRoutes = registerDatabasesRoutes;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const common_1 = require("../../utils/common");
9
+ const databases_service_1 = require("./databases.service");
10
+ function registerDatabasesRoutes(router, ctx) {
11
+ const { manager } = ctx;
12
+ const allowBrowse = ctx.allowBrowse ?? true;
13
+ const browseRoot = ctx.browseRoot ? node_path_1.default.resolve(ctx.browseRoot) : undefined;
14
+ const wrap = (fn) => async (req, res) => {
15
+ try {
16
+ await fn(req, res);
17
+ }
18
+ catch (err) {
19
+ res.status(500).json({ success: false, error: (0, common_1.errorMessage)(err) });
20
+ }
21
+ };
22
+ router.get('/', async (_req, res, next) => {
23
+ try {
24
+ res.locals.databasesMode = true;
25
+ res.render('pages/databases', {
26
+ title: 'Databases',
27
+ databasesMode: true,
28
+ databases: await databases_service_1.DatabasesService.getDatabaseRows(manager),
29
+ dbDir: manager.directory,
30
+ readonly: Boolean(ctx.readonly),
31
+ allowBrowse,
32
+ browseRoot,
33
+ config: { basePath: ctx.basePath },
34
+ });
35
+ }
36
+ catch (err) {
37
+ next(err);
38
+ }
39
+ });
40
+ router.get('/api/databases', wrap(async (_req, res) => {
41
+ res.json({ success: true, data: await databases_service_1.DatabasesService.getDatabaseRows(manager) });
42
+ }));
43
+ router.get('/api/fs/list', wrap(async (req, res) => {
44
+ if (!allowBrowse) {
45
+ return res.status(403).json({
46
+ success: false,
47
+ error: 'File browsing is disabled — this server was started with specific database files.',
48
+ });
49
+ }
50
+ const raw = String(req.query.path ?? '').trim();
51
+ const base = raw ? node_path_1.default.resolve(raw) : manager.directory;
52
+ try {
53
+ const data = databases_service_1.DatabasesService.listDirectory(base, manager, browseRoot);
54
+ res.json({ success: true, data });
55
+ }
56
+ catch (err) {
57
+ const msg = (0, common_1.errorMessage)(err);
58
+ if (msg.includes('outside the allowed folder'))
59
+ return res.status(403).json({ success: false, error: msg });
60
+ if (msg.includes('does not exist'))
61
+ return res.status(404).json({ success: false, error: msg });
62
+ return res.status(400).json({ success: false, error: msg });
63
+ }
64
+ }));
65
+ router.post('/api/databases/open', wrap(async (req, res) => {
66
+ if (!allowBrowse) {
67
+ return res.status(403).json({
68
+ success: false,
69
+ error: 'File browsing is disabled — this server was started with specific database files.',
70
+ });
71
+ }
72
+ const p = String(req.body?.path ?? '').trim();
73
+ const isRo = req.body?.readonly !== undefined ? Boolean(req.body.readonly) : undefined;
74
+ try {
75
+ const id = databases_service_1.DatabasesService.openDatabaseFile(manager, p, isRo, browseRoot);
76
+ res.json({ success: true, data: { id, message: `Opened "${id}".` } });
77
+ }
78
+ catch (err) {
79
+ const msg = (0, common_1.errorMessage)(err);
80
+ if (msg.includes('outside the allowed folder'))
81
+ return res.status(403).json({ success: false, error: msg });
82
+ return res.status(400).json({ success: false, error: msg });
83
+ }
84
+ }));
85
+ router.post('/api/databases/connect-postgres', wrap(async (req, res) => {
86
+ if (ctx.readonly)
87
+ return res.status(403).json({ success: false, error: 'Read-only mode — adding connections is disabled.' });
88
+ const name = String(req.body?.name ?? '').trim();
89
+ const connectionString = String(req.body?.connectionString ?? req.body?.connection ?? '').trim();
90
+ const isRo = Boolean(req.body?.readonly);
91
+ try {
92
+ const id = await databases_service_1.DatabasesService.connectPostgres(manager, name, connectionString, isRo);
93
+ res.status(201).json({ success: true, data: { id, message: `Connected to PostgreSQL database "${id}".` } });
94
+ }
95
+ catch (err) {
96
+ res.status(400).json({ success: false, error: (0, common_1.errorMessage)(err) });
97
+ }
98
+ }));
99
+ router.post('/api/databases/:id/mode', wrap(async (req, res) => {
100
+ if (ctx.readonly && req.body?.readonly === false) {
101
+ return res.status(403).json({ success: false, error: 'Server is in global read-only mode.' });
102
+ }
103
+ const id = String(req.params.id);
104
+ const targetRo = Boolean(req.body?.readonly);
105
+ try {
106
+ databases_service_1.DatabasesService.setDatabaseMode(manager, id, targetRo, ctx.invalidate);
107
+ res.json({
108
+ success: true,
109
+ data: {
110
+ id,
111
+ readonly: targetRo,
112
+ message: `Database "${id}" mode set to ${targetRo ? 'Read-only' : 'Writable'}.`,
113
+ },
114
+ });
115
+ }
116
+ catch (err) {
117
+ const msg = (0, common_1.errorMessage)(err);
118
+ if (msg.includes('does not exist'))
119
+ return res.status(404).json({ success: false, error: msg });
120
+ res.status(400).json({ success: false, error: msg });
121
+ }
122
+ }));
123
+ router.post('/api/databases', wrap(async (req, res) => {
124
+ if (ctx.readonly)
125
+ return res.status(403).json({ success: false, error: 'Read-only mode — creating databases is disabled.' });
126
+ const name = String(req.body?.name ?? '').trim();
127
+ try {
128
+ const id = databases_service_1.DatabasesService.createDatabase(manager, name);
129
+ res.status(201).json({ success: true, data: { id, message: `Database "${id}" created.` } });
130
+ }
131
+ catch (err) {
132
+ res.status(400).json({ success: false, error: (0, common_1.errorMessage)(err) });
133
+ }
134
+ }));
135
+ router.delete('/api/databases/:id', wrap(async (req, res) => {
136
+ if (ctx.readonly)
137
+ return res.status(403).json({ success: false, error: 'Read-only mode — deleting databases is disabled.' });
138
+ const id = String(req.params.id);
139
+ try {
140
+ databases_service_1.DatabasesService.removeDatabase(manager, id, ctx.invalidate);
141
+ res.json({ success: true, data: { message: `Database "${id}" removed.` } });
142
+ }
143
+ catch (err) {
144
+ const msg = (0, common_1.errorMessage)(err);
145
+ if (msg.includes('does not exist'))
146
+ return res.status(404).json({ success: false, error: msg });
147
+ res.status(400).json({ success: false, error: msg });
148
+ }
149
+ }));
150
+ }
@@ -0,0 +1 @@
1
+ import type {DbManager} from '../../db/manager';import type {DatabaseRow,FsEntry} from '../../types/api';export type {DatabaseRow,FsEntry};export declare class DatabasesService{static getDatabaseRows(manager:DbManager):Promise<DatabaseRow[]>;static listDirectory(base:string,manager:DbManager,browseRoot?:string):{path:string;name:string;parent:string|null;entries:FsEntry[];};static openDatabaseFile(manager:DbManager,filePath:string,isRo?:boolean,browseRoot?:string):string;static connectPostgres(manager:DbManager,name:string,connectionString:string,isRo?:boolean):Promise<string>;static createDatabase(manager:DbManager,name:string):string;static setDatabaseMode(manager:DbManager,id:string,readonly :boolean,invalidate?:(id:string)=>void):void;static removeDatabase(manager:DbManager,id:string,invalidate?:(id:string)=>void):void;}