admindb 2.1.0 → 2.1.1

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 (178) hide show
  1. package/README.md +1 -1
  2. package/dist/app.d.ts +1 -69
  3. package/dist/app.js +1 -257
  4. package/dist/auth/config.d.ts +1 -19
  5. package/dist/auth/config.js +1 -147
  6. package/dist/auth/crypto.d.ts +1 -27
  7. package/dist/auth/crypto.js +1 -133
  8. package/dist/auth/index.d.ts +1 -5
  9. package/dist/auth/index.js +1 -22
  10. package/dist/auth/middleware.d.ts +1 -14
  11. package/dist/auth/middleware.js +1 -100
  12. package/dist/auth/routes.d.ts +1 -7
  13. package/dist/auth/routes.js +1 -78
  14. package/dist/auth/types.d.ts +1 -35
  15. package/dist/auth/types.js +1 -11
  16. package/dist/cli/args.d.ts +1 -45
  17. package/dist/cli/args.js +1 -328
  18. package/dist/cli/config.d.ts +1 -56
  19. package/dist/cli/config.js +1 -133
  20. package/dist/cli/index.d.ts +1 -4
  21. package/dist/cli/index.js +1 -21
  22. package/dist/cli/runner.d.ts +1 -1
  23. package/dist/cli/runner.js +1 -222
  24. package/dist/cli.d.ts +1 -2
  25. package/dist/cli.js +1 -5
  26. package/dist/data/datasets.d.ts +1 -17
  27. package/dist/data/datasets.js +1 -133
  28. package/dist/data/detector.d.ts +1 -30
  29. package/dist/data/detector.js +1 -472
  30. package/dist/data/engine.d.ts +1 -10
  31. package/dist/data/engine.js +1 -211
  32. package/dist/data/index.d.ts +1 -5
  33. package/dist/data/index.js +1 -22
  34. package/dist/data/strategies.d.ts +1 -30
  35. package/dist/data/strategies.js +1 -343
  36. package/dist/data/types.d.ts +1 -73
  37. package/dist/data/types.js +1 -8
  38. package/dist/db/database.d.ts +1 -119
  39. package/dist/db/database.js +1 -471
  40. package/dist/db/export.d.ts +1 -12
  41. package/dist/db/export.js +1 -42
  42. package/dist/db/filters.d.ts +1 -22
  43. package/dist/db/filters.js +1 -158
  44. package/dist/db/index.d.ts +1 -8
  45. package/dist/db/index.js +1 -25
  46. package/dist/db/introspection.d.ts +1 -6
  47. package/dist/db/introspection.js +1 -172
  48. package/dist/db/manager.d.ts +1 -81
  49. package/dist/db/manager.js +1 -397
  50. package/dist/db/migrations.d.ts +1 -5
  51. package/dist/db/migrations.js +1 -110
  52. package/dist/db/postgres.d.ts +1 -85
  53. package/dist/db/postgres.js +1 -745
  54. package/dist/db/types.d.ts +1 -206
  55. package/dist/db/types.js +1 -7
  56. package/dist/index.d.ts +1 -23
  57. package/dist/index.js +1 -66
  58. package/dist/public/js/api.js +1 -47
  59. package/dist/public/js/app.js +1 -262
  60. package/dist/public/js/browse.js +1 -1371
  61. package/dist/public/js/databases.js +1 -235
  62. package/dist/public/js/designer.js +1 -258
  63. package/dist/public/js/forms.js +1 -826
  64. package/dist/public/js/icons.js +1 -51
  65. package/dist/public/js/inspector.js +1 -638
  66. package/dist/public/js/query.js +1 -322
  67. package/dist/public/js/schema.js +1 -447
  68. package/dist/public/js/seed.js +1 -1090
  69. package/dist/routes/api/helpers.d.ts +1 -42
  70. package/dist/routes/api/helpers.js +1 -133
  71. package/dist/routes/api/import-export.d.ts +1 -3
  72. package/dist/routes/api/import-export.js +1 -85
  73. package/dist/routes/api/index.d.ts +1 -4
  74. package/dist/routes/api/index.js +1 -37
  75. package/dist/routes/api/query.d.ts +1 -3
  76. package/dist/routes/api/query.js +1 -76
  77. package/dist/routes/api/rows.d.ts +1 -3
  78. package/dist/routes/api/rows.js +1 -373
  79. package/dist/routes/api/seed.d.ts +1 -3
  80. package/dist/routes/api/seed.js +1 -82
  81. package/dist/routes/api/tables.d.ts +1 -3
  82. package/dist/routes/api/tables.js +1 -160
  83. package/dist/routes/databases.d.ts +1 -26
  84. package/dist/routes/databases.js +1 -230
  85. package/dist/routes/index.d.ts +1 -3
  86. package/dist/routes/index.js +1 -20
  87. package/dist/routes/pages.d.ts +1 -9
  88. package/dist/routes/pages.js +1 -390
  89. package/dist/serverless.d.ts +1 -40
  90. package/dist/serverless.js +1 -210
  91. package/dist/sql/classifier.d.ts +1 -19
  92. package/dist/sql/classifier.js +1 -37
  93. package/dist/sql/generator.d.ts +1 -81
  94. package/dist/sql/generator.js +1 -348
  95. package/dist/sql/index.d.ts +1 -2
  96. package/dist/sql/index.js +1 -19
  97. package/dist/types/api.d.ts +1 -412
  98. package/dist/types/api.js +1 -9
  99. package/dist/utils/colors.d.ts +1 -40
  100. package/dist/utils/colors.js +1 -64
  101. package/dist/utils/common.d.ts +1 -50
  102. package/dist/utils/common.js +1 -229
  103. package/dist/utils/csv.d.ts +1 -15
  104. package/dist/utils/csv.js +1 -80
  105. package/dist/utils/datatype.d.ts +1 -49
  106. package/dist/utils/datatype.js +1 -222
  107. package/dist/utils/icons.d.ts +1 -11
  108. package/dist/utils/icons.js +1 -72
  109. package/dist/utils/index.d.ts +1 -4
  110. package/dist/utils/index.js +1 -21
  111. package/dist/utils/logger.d.ts +1 -10
  112. package/dist/utils/logger.js +1 -40
  113. package/dist/views/layouts/main.hbs +0 -4
  114. package/dist/views/pages/databases.hbs +0 -18
  115. package/dist/views/pages/designer.hbs +0 -10
  116. package/dist/views/pages/error.hbs +0 -2
  117. package/dist/views/pages/form.hbs +0 -5
  118. package/dist/views/pages/home.hbs +0 -8
  119. package/dist/views/pages/login.hbs +0 -13
  120. package/dist/views/pages/query.hbs +0 -9
  121. package/dist/views/pages/schema.hbs +0 -20
  122. package/dist/views/pages/seed.hbs +0 -31
  123. package/dist/views/pages/table.hbs +0 -14
  124. package/dist/views/partials/icon.hbs +0 -1
  125. package/dist/views/partials/navbar.hbs +0 -6
  126. package/dist/views/partials/sidebar.hbs +0 -8
  127. package/package.json +3 -2
  128. package/dist/app.js.map +0 -1
  129. package/dist/auth/config.js.map +0 -1
  130. package/dist/auth/crypto.js.map +0 -1
  131. package/dist/auth/index.js.map +0 -1
  132. package/dist/auth/middleware.js.map +0 -1
  133. package/dist/auth/routes.js.map +0 -1
  134. package/dist/auth/types.js.map +0 -1
  135. package/dist/cli/args.js.map +0 -1
  136. package/dist/cli/config.js.map +0 -1
  137. package/dist/cli/index.js.map +0 -1
  138. package/dist/cli/runner.js.map +0 -1
  139. package/dist/cli.js.map +0 -1
  140. package/dist/data/datasets.js.map +0 -1
  141. package/dist/data/detector.js.map +0 -1
  142. package/dist/data/engine.js.map +0 -1
  143. package/dist/data/index.js.map +0 -1
  144. package/dist/data/strategies.js.map +0 -1
  145. package/dist/data/types.js.map +0 -1
  146. package/dist/db/database.js.map +0 -1
  147. package/dist/db/export.js.map +0 -1
  148. package/dist/db/filters.js.map +0 -1
  149. package/dist/db/index.js.map +0 -1
  150. package/dist/db/introspection.js.map +0 -1
  151. package/dist/db/manager.js.map +0 -1
  152. package/dist/db/migrations.js.map +0 -1
  153. package/dist/db/postgres.js.map +0 -1
  154. package/dist/db/types.js.map +0 -1
  155. package/dist/index.js.map +0 -1
  156. package/dist/public/css/input.css +0 -302
  157. package/dist/routes/api/helpers.js.map +0 -1
  158. package/dist/routes/api/import-export.js.map +0 -1
  159. package/dist/routes/api/index.js.map +0 -1
  160. package/dist/routes/api/query.js.map +0 -1
  161. package/dist/routes/api/rows.js.map +0 -1
  162. package/dist/routes/api/seed.js.map +0 -1
  163. package/dist/routes/api/tables.js.map +0 -1
  164. package/dist/routes/databases.js.map +0 -1
  165. package/dist/routes/index.js.map +0 -1
  166. package/dist/routes/pages.js.map +0 -1
  167. package/dist/serverless.js.map +0 -1
  168. package/dist/sql/classifier.js.map +0 -1
  169. package/dist/sql/generator.js.map +0 -1
  170. package/dist/sql/index.js.map +0 -1
  171. package/dist/types/api.js.map +0 -1
  172. package/dist/utils/colors.js.map +0 -1
  173. package/dist/utils/common.js.map +0 -1
  174. package/dist/utils/csv.js.map +0 -1
  175. package/dist/utils/datatype.js.map +0 -1
  176. package/dist/utils/icons.js.map +0 -1
  177. package/dist/utils/index.js.map +0 -1
  178. package/dist/utils/logger.js.map +0 -1
@@ -1,745 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PostgresDatabase = void 0;
4
- const pg_1 = require("pg");
5
- const generator_1 = require("../sql/generator");
6
- const common_1 = require("../utils/common");
7
- const types_1 = require("./types");
8
- const filters_1 = require("./filters");
9
- class PostgresDatabase {
10
- pool;
11
- logger;
12
- path;
13
- isReadOnly;
14
- dialect = 'postgres';
15
- schema;
16
- constructor(connection, logger, options = {}) {
17
- this.logger = logger;
18
- const opts = typeof connection === 'string'
19
- ? { connectionString: connection, ...options }
20
- : { ...connection, ...options };
21
- this.isReadOnly = Boolean(opts.readonly);
22
- this.schema = opts.schema || 'public';
23
- let sslConfig = opts.ssl;
24
- if (opts.connectionString) {
25
- try {
26
- const u = new URL(opts.connectionString);
27
- const sslMode = (u.searchParams.get('sslmode') || u.searchParams.get('ssl') || '').toLowerCase();
28
- const host = u.hostname.toLowerCase();
29
- const isLocal = host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '0.0.0.0';
30
- if (sslMode === 'disable') {
31
- sslConfig = false;
32
- }
33
- else if (sslConfig === undefined) {
34
- // Auto-enable SSL for remote/cloud hosts (e.g. Neon, Supabase, Render, Railway, AWS RDS, etc.)
35
- // or whenever sslmode is specified or not explicitly local
36
- if (sslMode === 'require' || sslMode === 'prefer' || sslMode === 'no-verify' || !isLocal) {
37
- sslConfig = { rejectUnauthorized: false };
38
- }
39
- }
40
- }
41
- catch {
42
- /* ignore URL parse error */
43
- }
44
- }
45
- else if (opts.host && sslConfig === undefined) {
46
- const host = opts.host.toLowerCase();
47
- const isLocal = host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '0.0.0.0';
48
- if (!isLocal) {
49
- sslConfig = { rejectUnauthorized: false };
50
- }
51
- }
52
- const poolConfig = {
53
- connectionString: opts.connectionString,
54
- host: opts.host,
55
- port: opts.port,
56
- database: opts.database,
57
- user: opts.user,
58
- password: opts.password,
59
- ssl: sslConfig,
60
- max: opts.max ?? 10,
61
- connectionTimeoutMillis: 10000,
62
- idleTimeoutMillis: 30000,
63
- keepAlive: true,
64
- };
65
- this.path = opts.connectionString
66
- ? this.sanitizeConnectionString(opts.connectionString)
67
- : `${opts.user || 'postgres'}@${opts.host || 'localhost'}:${opts.port || 5432}/${opts.database || 'postgres'}`;
68
- this.pool = new pg_1.Pool(poolConfig);
69
- this.pool.on('error', (err) => {
70
- this.logger.error(`Unexpected PostgreSQL client error: ${(0, common_1.errorMessage)(err)}`);
71
- });
72
- if (!this.isReadOnly) {
73
- this.initSchema().catch((err) => {
74
- this.logger.warn(`Failed to initialize Postgres schema: ${(0, common_1.errorMessage)(err)}`);
75
- });
76
- }
77
- this.logger.info(`Initialized PostgreSQL pool for ${this.path}${this.isReadOnly ? ' (read-only)' : ''}`);
78
- }
79
- sanitizeConnectionString(conn) {
80
- try {
81
- const url = new URL(conn);
82
- if (url.password)
83
- url.password = '****';
84
- return url.toString();
85
- }
86
- catch {
87
- return conn.replace(/:([^@]+)@/, ':****@');
88
- }
89
- }
90
- readonlyBlocked() {
91
- return { success: false, error: 'Database is open in read-only mode — write operations are disabled.' };
92
- }
93
- async close() {
94
- try {
95
- await this.pool.end();
96
- }
97
- catch (err) {
98
- this.logger.warn(`Failed to close PostgreSQL pool: ${(0, common_1.errorMessage)(err)}`);
99
- }
100
- }
101
- async tryRun(fn) {
102
- try {
103
- const data = await fn();
104
- return { success: true, data };
105
- }
106
- catch (err) {
107
- const msg = (0, common_1.errorMessage)(err);
108
- this.logger.error(msg);
109
- return { success: false, error: msg };
110
- }
111
- }
112
- /** Run a callback inside an atomic transaction on a dedicated client. */
113
- async transaction(fn) {
114
- if (this.isReadOnly)
115
- return this.readonlyBlocked();
116
- const client = await this.pool.connect();
117
- try {
118
- await client.query('BEGIN');
119
- const res = await fn(client);
120
- await client.query('COMMIT');
121
- return { success: true, data: res };
122
- }
123
- catch (err) {
124
- try {
125
- await client.query('ROLLBACK');
126
- }
127
- catch {
128
- /* ignore */
129
- }
130
- const msg = (0, common_1.errorMessage)(err);
131
- this.logger.error(msg);
132
- return { success: false, error: msg };
133
- }
134
- finally {
135
- client.release();
136
- }
137
- }
138
- async initSchema() {
139
- const sql = `
140
- CREATE TABLE IF NOT EXISTS ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} (
141
- id SERIAL PRIMARY KEY,
142
- name VARCHAR(255) NOT NULL UNIQUE,
143
- sql TEXT NOT NULL,
144
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
145
- );
146
- `;
147
- await this.pool.query(sql);
148
- }
149
- hasMultipleStatements(sql) {
150
- const s = String(sql ?? '').trim();
151
- if (!s)
152
- return false;
153
- let count = 0;
154
- let inString = false;
155
- for (let i = 0; i < s.length; i++) {
156
- const ch = s[i];
157
- if (ch === "'") {
158
- if (inString && s[i + 1] === "'") {
159
- i++;
160
- continue;
161
- }
162
- inString = !inString;
163
- }
164
- else if (ch === ';' && !inString) {
165
- // Only count if there are non-whitespace characters following
166
- const rest = s.slice(i + 1).trim();
167
- if (rest.length > 0)
168
- count++;
169
- }
170
- }
171
- return count > 0;
172
- }
173
- async all(sql, params = []) {
174
- return this.tryRun(async () => {
175
- const pgSql = (0, filters_1.convertPlaceholdersToPostgres)(sql);
176
- const res = await this.pool.query(pgSql, params);
177
- return res.rows;
178
- });
179
- }
180
- async run(sql, params = []) {
181
- if (this.isReadOnly)
182
- return this.readonlyBlocked();
183
- return this.tryRun(async () => {
184
- const pgSql = (0, filters_1.convertPlaceholdersToPostgres)(sql);
185
- const res = await this.pool.query(pgSql, params);
186
- return {
187
- changes: res.rowCount ?? 0,
188
- lastInsertRowid: null,
189
- };
190
- });
191
- }
192
- async execResult(sql) {
193
- if (this.isReadOnly)
194
- return this.readonlyBlocked();
195
- return this.tryRun(async () => {
196
- const res = await this.pool.query(sql);
197
- const changes = Array.isArray(res)
198
- ? res.reduce((acc, r) => acc + (r.rowCount ?? 0), 0)
199
- : res.rowCount ?? undefined;
200
- return { changes };
201
- });
202
- }
203
- async runWrite(sql) {
204
- if (this.isReadOnly)
205
- return this.readonlyBlocked();
206
- return this.tryRun(async () => {
207
- const res = await this.pool.query(sql);
208
- const changes = Array.isArray(res)
209
- ? res.reduce((acc, r) => acc + (r.rowCount ?? 0), 0)
210
- : res.rowCount ?? undefined;
211
- return { changes };
212
- });
213
- }
214
- async listTables() {
215
- return this.tryRun(async () => {
216
- const res = await this.pool.query(`SELECT table_name AS name
217
- FROM information_schema.tables
218
- WHERE table_schema = $1 AND table_type = 'BASE TABLE'
219
- ORDER BY table_name;`, [this.schema]);
220
- return res.rows.map((r) => ({ name: r.name }));
221
- });
222
- }
223
- async getTableInfo(table) {
224
- return this.tryRun(async () => {
225
- // Execute all 4 metadata queries concurrently in parallel
226
- const [colRes, pkRes, fkRes, idxRes] = await Promise.all([
227
- // 1. Columns
228
- this.pool.query(`SELECT
229
- ordinal_position AS cid,
230
- column_name AS name,
231
- data_type AS type,
232
- udt_name,
233
- CASE WHEN is_nullable = 'NO' THEN 1 ELSE 0 END AS notnull,
234
- column_default AS dflt_value
235
- FROM information_schema.columns
236
- WHERE table_schema = $1 AND table_name = $2
237
- ORDER BY ordinal_position;`, [this.schema, table]),
238
- // 2. Primary Keys
239
- this.pool.query(`SELECT kcu.column_name
240
- FROM information_schema.table_constraints tc
241
- JOIN information_schema.key_column_usage kcu
242
- ON tc.constraint_name = kcu.constraint_name
243
- AND tc.table_schema = kcu.table_schema
244
- WHERE tc.constraint_type = 'PRIMARY KEY'
245
- AND tc.table_schema = $1
246
- AND tc.table_name = $2
247
- ORDER BY kcu.ordinal_position;`, [this.schema, table]),
248
- // 3. Foreign Keys
249
- this.pool.query(`SELECT
250
- row_number() OVER () AS id,
251
- kcu.position_in_unique_constraint AS seq,
252
- ccu.table_name AS table,
253
- kcu.column_name AS "from",
254
- ccu.column_name AS "to",
255
- rc.update_rule AS on_update,
256
- rc.delete_rule AS on_delete
257
- FROM information_schema.table_constraints AS tc
258
- JOIN information_schema.key_column_usage AS kcu
259
- ON tc.constraint_name = kcu.constraint_name
260
- AND tc.table_schema = kcu.table_schema
261
- JOIN information_schema.constraint_column_usage AS ccu
262
- ON ccu.constraint_name = tc.constraint_name
263
- AND ccu.table_schema = tc.table_schema
264
- JOIN information_schema.referential_constraints AS rc
265
- ON rc.constraint_name = tc.constraint_name
266
- AND rc.constraint_schema = tc.table_schema
267
- WHERE tc.constraint_type = 'FOREIGN KEY'
268
- AND tc.table_schema = $1
269
- AND tc.table_name = $2;`, [this.schema, table]),
270
- // 4. Indexes
271
- this.pool.query(`SELECT
272
- i.relname AS name,
273
- ix.indisunique AS unique,
274
- pg_get_indexdef(ix.indexrelid) AS sql,
275
- ARRAY(
276
- SELECT pg_get_indexdef(ix.indexrelid, k + 1, true)
277
- FROM generate_subscripts(ix.indkey, 1) as k
278
- ORDER BY k
279
- ) AS columns
280
- FROM pg_index ix
281
- JOIN pg_class t ON t.oid = ix.indrelid
282
- JOIN pg_class i ON i.oid = ix.indexrelid
283
- JOIN pg_namespace n ON n.oid = t.relnamespace
284
- WHERE n.nspname = $1 AND t.relname = $2;`, [this.schema, table]),
285
- ]);
286
- const primaryKey = pkRes.rows.map((r) => r.column_name);
287
- const columns = colRes.rows.map((c) => {
288
- const isPk = primaryKey.includes(c.name);
289
- const displayType = c.type === 'USER-DEFINED' ? c.udt_name : c.type;
290
- return {
291
- cid: c.cid,
292
- name: c.name,
293
- type: displayType.toUpperCase(),
294
- notnull: c.notnull,
295
- dflt_value: c.dflt_value,
296
- pk: isPk ? primaryKey.indexOf(c.name) + 1 : 0,
297
- };
298
- });
299
- const indexes = idxRes.rows.map((ix) => ({
300
- name: ix.name,
301
- unique: Boolean(ix.unique),
302
- partial: 0,
303
- origin: 'c',
304
- columns: Array.isArray(ix.columns) ? ix.columns : [],
305
- sql: ix.sql,
306
- }));
307
- // Approximate DDL for display
308
- const colDefs = columns.map((c) => {
309
- let def = ` ${(0, generator_1.quoteIdentifier)(c.name)} ${c.type}`;
310
- if (c.notnull)
311
- def += ' NOT NULL';
312
- if (c.dflt_value)
313
- def += ` DEFAULT ${c.dflt_value}`;
314
- return def;
315
- });
316
- if (primaryKey.length) {
317
- colDefs.push(` PRIMARY KEY (${primaryKey.map(generator_1.quoteIdentifier).join(', ')})`);
318
- }
319
- const synthesizedSql = `CREATE TABLE ${(0, generator_1.quoteIdentifier)(table)} (\n${colDefs.join(',\n')}\n);`;
320
- return {
321
- table,
322
- columns,
323
- foreignKeys: fkRes.rows.map((f) => ({
324
- id: Number(f.id),
325
- seq: Number(f.seq ?? 1),
326
- table: f.table,
327
- from: f.from,
328
- to: f.to,
329
- on_update: f.on_update,
330
- on_delete: f.on_delete,
331
- })),
332
- primaryKey,
333
- indexes,
334
- sql: synthesizedSql,
335
- };
336
- });
337
- }
338
- async getRowCount(table, filters) {
339
- return this.tryRun(async () => {
340
- let pgWhere = '';
341
- let params = [];
342
- if (filters && Object.keys(filters).length > 0) {
343
- const { where, params: p } = (0, filters_1.buildFilterClause)(filters, Object.keys(filters));
344
- pgWhere = (0, filters_1.convertPlaceholdersToPostgres)(where);
345
- params = p;
346
- }
347
- const sql = `SELECT COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(table)}${pgWhere}`;
348
- const res = await this.pool.query(sql, params);
349
- return Number(res.rows[0]?.c ?? 0);
350
- });
351
- }
352
- async getRows(table, opts = {}) {
353
- return this.tryRun(async () => {
354
- const orderCol = opts.orderBy;
355
- const orderDir = opts.orderDir === 'desc' ? 'DESC' : 'ASC';
356
- const limit = opts.limit && opts.limit > 0 ? opts.limit : 200;
357
- const page = opts.page && opts.page > 0 ? opts.page : 1;
358
- const offset = (page - 1) * limit;
359
- let pgWhere = '';
360
- let filterParams = [];
361
- if (opts.filters && Object.keys(opts.filters).length > 0) {
362
- const { where, params } = (0, filters_1.buildFilterClause)(opts.filters, Object.keys(opts.filters));
363
- pgWhere = (0, filters_1.convertPlaceholdersToPostgres)(where);
364
- filterParams = params;
365
- }
366
- const nextParamIdx = filterParams.length + 1;
367
- let sql = `SELECT * FROM ${(0, generator_1.quoteIdentifier)(table)}${pgWhere}`;
368
- if (orderCol) {
369
- sql += ` ORDER BY ${(0, generator_1.quoteIdentifier)(orderCol)} ${orderDir}`;
370
- }
371
- sql += ` LIMIT $${nextParamIdx} OFFSET $${nextParamIdx + 1}`;
372
- const allParams = [...filterParams, limit, offset];
373
- const res = await this.pool.query(sql, allParams);
374
- return res.rows;
375
- });
376
- }
377
- async getAllRows(table) {
378
- return this.tryRun(async () => {
379
- const res = await this.pool.query(`SELECT * FROM ${(0, generator_1.quoteIdentifier)(table)}`);
380
- return res.rows;
381
- });
382
- }
383
- async getRow(table, where) {
384
- return this.tryRun(async () => {
385
- const conds = where.map((w, idx) => `${(0, generator_1.quoteIdentifier)(w.column)} = $${idx + 1}`);
386
- const sql = `SELECT * FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${conds.join(' AND ')} LIMIT 1`;
387
- const res = await this.pool.query(sql, where.map((w) => w.value));
388
- return res.rows[0] ?? null;
389
- });
390
- }
391
- async insertRow(table, fields) {
392
- if (this.isReadOnly)
393
- return this.readonlyBlocked();
394
- return this.tryRun(async () => {
395
- const cols = fields.map((f) => (0, generator_1.quoteIdentifier)(f.column));
396
- const placeholders = fields.map((_, idx) => `$${idx + 1}`);
397
- const sql = `INSERT INTO ${(0, generator_1.quoteIdentifier)(table)} (${cols.join(', ')}) VALUES (${placeholders.join(', ')}) RETURNING *`;
398
- const res = await this.pool.query(sql, fields.map((f) => f.value));
399
- return {
400
- changes: res.rowCount ?? 1,
401
- lastInsertRowid: null,
402
- };
403
- });
404
- }
405
- async insertRows(table, rows) {
406
- return this.transaction(async (client) => {
407
- let inserted = 0;
408
- for (const fields of rows) {
409
- if (!fields.length)
410
- continue;
411
- const cols = fields.map((f) => (0, generator_1.quoteIdentifier)(f.column));
412
- const placeholders = fields.map((_, idx) => `$${idx + 1}`);
413
- const sql = `INSERT INTO ${(0, generator_1.quoteIdentifier)(table)} (${cols.join(', ')}) VALUES (${placeholders.join(', ')})`;
414
- await client.query(sql, fields.map((f) => f.value));
415
- inserted++;
416
- }
417
- return { inserted, skipped: rows.length - inserted };
418
- });
419
- }
420
- async updateRow(table, fields, where) {
421
- if (this.isReadOnly)
422
- return this.readonlyBlocked();
423
- return this.tryRun(async () => {
424
- const sets = fields.map((f, idx) => `${(0, generator_1.quoteIdentifier)(f.column)} = $${idx + 1}`);
425
- const conds = where.map((w, idx) => `${(0, generator_1.quoteIdentifier)(w.column)} = $${fields.length + idx + 1}`);
426
- const sql = `UPDATE ${(0, generator_1.quoteIdentifier)(table)} SET ${sets.join(', ')} WHERE ${conds.join(' AND ')}`;
427
- const res = await this.pool.query(sql, [...fields.map((f) => f.value), ...where.map((w) => w.value)]);
428
- return {
429
- changes: res.rowCount ?? 0,
430
- lastInsertRowid: null,
431
- };
432
- });
433
- }
434
- async updateRows(table, rows) {
435
- return this.transaction(async (client) => {
436
- let updated = 0;
437
- for (const { fields, where } of rows) {
438
- if (!fields.length || !where.length)
439
- continue;
440
- const sets = fields.map((f, idx) => `${(0, generator_1.quoteIdentifier)(f.column)} = $${idx + 1}`);
441
- const conds = where.map((w, idx) => `${(0, generator_1.quoteIdentifier)(w.column)} = $${fields.length + idx + 1}`);
442
- const sql = `UPDATE ${(0, generator_1.quoteIdentifier)(table)} SET ${sets.join(', ')} WHERE ${conds.join(' AND ')}`;
443
- const res = await client.query(sql, [...fields.map((f) => f.value), ...where.map((w) => w.value)]);
444
- updated += res.rowCount ?? 0;
445
- }
446
- return { updated };
447
- });
448
- }
449
- async deleteRow(table, where) {
450
- if (this.isReadOnly)
451
- return this.readonlyBlocked();
452
- return this.tryRun(async () => {
453
- const conds = where.map((w, idx) => `${(0, generator_1.quoteIdentifier)(w.column)} = $${idx + 1}`);
454
- const sql = `DELETE FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${conds.join(' AND ')}`;
455
- const res = await this.pool.query(sql, where.map((w) => w.value));
456
- return {
457
- changes: res.rowCount ?? 0,
458
- lastInsertRowid: null,
459
- };
460
- });
461
- }
462
- async deleteRows(table, rows) {
463
- return this.transaction(async (client) => {
464
- let deleted = 0;
465
- for (const where of rows) {
466
- if (!where.length)
467
- continue;
468
- const conds = where.map((w, idx) => `${(0, generator_1.quoteIdentifier)(w.column)} = $${idx + 1}`);
469
- const sql = `DELETE FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${conds.join(' AND ')}`;
470
- const res = await client.query(sql, where.map((w) => w.value));
471
- deleted += res.rowCount ?? 0;
472
- }
473
- return { deleted };
474
- });
475
- }
476
- async getRowsByPks(table, pks) {
477
- return this.tryRun(async () => {
478
- const groups = [];
479
- const params = [];
480
- let paramIdx = 1;
481
- for (const where of pks) {
482
- if (!where.length)
483
- continue;
484
- const conds = where.map((w) => {
485
- params.push(w.value);
486
- return `${(0, generator_1.quoteIdentifier)(w.column)} = $${paramIdx++}`;
487
- });
488
- groups.push(conds.join(' AND '));
489
- }
490
- if (!groups.length)
491
- return [];
492
- const sql = `SELECT * FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${groups.map((g) => `(${g})`).join(' OR ')}`;
493
- const res = await this.pool.query(sql, params);
494
- return res.rows;
495
- });
496
- }
497
- async getRowsByFk(table, column, value, limit = 50) {
498
- return this.tryRun(async () => {
499
- const n = Math.max(1, Math.min(500, Number(limit) || 50));
500
- const countRes = await this.pool.query(`SELECT COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${(0, generator_1.quoteIdentifier)(column)} = $1`, [value]);
501
- const rowsRes = await this.pool.query(`SELECT * FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${(0, generator_1.quoteIdentifier)(column)} = $1 LIMIT $2`, [value, n]);
502
- return { rows: rowsRes.rows, total: Number(countRes.rows[0]?.c ?? 0) };
503
- });
504
- }
505
- async getCreateStatement(table) {
506
- const info = await this.getTableInfo(table);
507
- if (!info.success || !info.data)
508
- return { success: false, error: info.error };
509
- return { success: true, data: info.data.sql };
510
- }
511
- async listSavedQueries() {
512
- return this.tryRun(async () => {
513
- 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`);
514
- return res.rows;
515
- });
516
- }
517
- async saveQuery(name, sql) {
518
- return this.tryRun(async () => {
519
- 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]);
520
- return {
521
- changes: res.rowCount ?? 1,
522
- lastInsertRowid: Number(res.rows[0]?.id ?? null),
523
- };
524
- });
525
- }
526
- async deleteSavedQuery(id) {
527
- return this.tryRun(async () => {
528
- const res = await this.pool.query(`DELETE FROM ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} WHERE id = $1`, [Number(id)]);
529
- return {
530
- changes: res.rowCount ?? 0,
531
- lastInsertRowid: null,
532
- };
533
- });
534
- }
535
- async getSchema(table) {
536
- return this.tryRun(async () => {
537
- const infoR = await this.getTableInfo(table);
538
- if (!infoR.success || !infoR.data)
539
- throw new Error(infoR.error || `Table ${table} not found.`);
540
- const { columns, foreignKeys, indexes, primaryKey } = infoR.data;
541
- // Find references in other tables that point to this table
542
- const refRes = await this.pool.query(`SELECT
543
- tc.table_name AS table,
544
- kcu.column_name AS "from",
545
- ccu.column_name AS "to"
546
- FROM information_schema.table_constraints AS tc
547
- JOIN information_schema.key_column_usage AS kcu
548
- ON tc.constraint_name = kcu.constraint_name
549
- AND tc.table_schema = kcu.table_schema
550
- JOIN information_schema.constraint_column_usage AS ccu
551
- ON ccu.constraint_name = tc.constraint_name
552
- AND ccu.table_schema = tc.table_schema
553
- WHERE tc.constraint_type = 'FOREIGN KEY'
554
- AND tc.table_schema = $1
555
- AND ccu.table_name = $2;`, [this.schema, table]);
556
- const references = refRes.rows;
557
- const columnDetails = columns.map((col) => {
558
- const isPk = primaryKey.includes(col.name);
559
- const fk = foreignKeys.find((f) => f.from === col.name) ?? null;
560
- const matchingIndexes = indexes.filter((i) => i.columns.includes(col.name));
561
- const unique = matchingIndexes.some((i) => i.unique && i.columns.length === 1 && i.columns[0] === col.name);
562
- const indexed = matchingIndexes.length > 0;
563
- const referencedBy = references
564
- .filter((r) => r.to === col.name)
565
- .map((r) => ({ table: r.table, from: r.from }));
566
- const dropBlockers = [];
567
- if (isPk && primaryKey.length === 1)
568
- dropBlockers.push('Primary key');
569
- if (referencedBy.length > 0) {
570
- dropBlockers.push(`Referenced by ${referencedBy.map((r) => `${r.table}.${r.from}`).join(', ')}`);
571
- }
572
- return {
573
- ...col,
574
- unique,
575
- indexed,
576
- referencedBy,
577
- fk,
578
- canDrop: dropBlockers.length === 0,
579
- dropBlockers,
580
- };
581
- });
582
- return {
583
- table,
584
- columns: columnDetails,
585
- foreignKeys,
586
- indexes,
587
- references,
588
- };
589
- });
590
- }
591
- async getReferencingTables(table) {
592
- return this.tryRun(async () => {
593
- const refRes = await this.pool.query(`SELECT
594
- tc.table_name AS table,
595
- kcu.column_name AS "from",
596
- ccu.column_name AS "to"
597
- FROM information_schema.table_constraints AS tc
598
- JOIN information_schema.key_column_usage AS kcu
599
- ON tc.constraint_name = kcu.constraint_name
600
- AND tc.table_schema = kcu.table_schema
601
- JOIN information_schema.constraint_column_usage AS ccu
602
- ON ccu.constraint_name = tc.constraint_name
603
- AND ccu.table_schema = tc.table_schema
604
- WHERE tc.constraint_type = 'FOREIGN KEY'
605
- AND tc.table_schema = $1
606
- AND ccu.table_name = $2;`, [this.schema, table]);
607
- const byTable = new Map();
608
- for (const r of refRes.rows) {
609
- if (!byTable.has(r.table))
610
- byTable.set(r.table, []);
611
- byTable.get(r.table).push({ from: r.from, to: r.to });
612
- }
613
- const results = [];
614
- for (const [tbl, refs] of byTable.entries()) {
615
- try {
616
- 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 ')}`);
617
- results.push({
618
- table: tbl,
619
- refs,
620
- refCount: Number(countRes.rows[0]?.c ?? 0),
621
- });
622
- }
623
- catch {
624
- results.push({ table: tbl, refs, refCount: 0 });
625
- }
626
- }
627
- return results;
628
- });
629
- }
630
- async renameTable(oldName, newName) {
631
- if (this.isReadOnly)
632
- return this.readonlyBlocked();
633
- return this.tryRun(async () => {
634
- await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(oldName)} RENAME TO ${(0, generator_1.quoteIdentifier)(newName)};`);
635
- return { changes: 1 };
636
- });
637
- }
638
- async addColumn(table, col) {
639
- if (this.isReadOnly)
640
- return this.readonlyBlocked();
641
- return this.tryRun(async () => {
642
- let sql = `ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ADD COLUMN ${(0, generator_1.quoteIdentifier)(col.name)} ${col.type}`;
643
- if (col.notNull)
644
- sql += ' NOT NULL';
645
- if (col.defaultValue !== undefined && col.defaultValue !== null && col.defaultValue !== '') {
646
- sql += ` DEFAULT ${col.defaultValue}`;
647
- }
648
- if (col.foreignKey) {
649
- sql += ` REFERENCES ${(0, generator_1.quoteIdentifier)(col.foreignKey.table)} (${(0, generator_1.quoteIdentifier)(col.foreignKey.column)})`;
650
- }
651
- await this.pool.query(sql);
652
- if (col.unique) {
653
- const idxName = `idx_${table}_${col.name}_unique`;
654
- 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)});`);
655
- }
656
- return { changes: 1 };
657
- });
658
- }
659
- async modifyColumn(table, oldColName, newColDef) {
660
- if (this.isReadOnly)
661
- return this.readonlyBlocked();
662
- return this.tryRun(async () => {
663
- const newName = String(newColDef.name || oldColName).trim();
664
- // Rename column first if name changed
665
- if (oldColName !== newName) {
666
- await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} RENAME COLUMN ${(0, generator_1.quoteIdentifier)(oldColName)} TO ${(0, generator_1.quoteIdentifier)(newName)};`);
667
- }
668
- const colToModify = newName;
669
- // Alter Type if provided
670
- if (newColDef.type) {
671
- 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};`);
672
- }
673
- // Alter NOT NULL
674
- if (newColDef.notNull !== undefined) {
675
- const action = newColDef.notNull ? 'SET NOT NULL' : 'DROP NOT NULL';
676
- await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0, generator_1.quoteIdentifier)(colToModify)} ${action};`);
677
- }
678
- // Alter DEFAULT
679
- if (newColDef.defaultValue !== undefined) {
680
- if (newColDef.defaultValue === null || newColDef.defaultValue === '') {
681
- await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0, generator_1.quoteIdentifier)(colToModify)} DROP DEFAULT;`);
682
- }
683
- else {
684
- await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0, generator_1.quoteIdentifier)(colToModify)} SET DEFAULT ${newColDef.defaultValue};`);
685
- }
686
- }
687
- // Handle unique index
688
- if (newColDef.unique !== undefined) {
689
- const idxName = `idx_${table}_${colToModify}_unique`;
690
- if (newColDef.unique) {
691
- 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)});`);
692
- }
693
- else {
694
- await this.pool.query(`DROP INDEX IF EXISTS ${(0, generator_1.quoteIdentifier)(idxName)};`);
695
- }
696
- }
697
- return { changes: 1 };
698
- });
699
- }
700
- async renameColumn(table, oldName, newName) {
701
- if (this.isReadOnly)
702
- return this.readonlyBlocked();
703
- return this.tryRun(async () => {
704
- await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} RENAME COLUMN ${(0, generator_1.quoteIdentifier)(oldName)} TO ${(0, generator_1.quoteIdentifier)(newName)};`);
705
- return { changes: 1 };
706
- });
707
- }
708
- async dropColumn(table, column) {
709
- if (this.isReadOnly)
710
- return this.readonlyBlocked();
711
- return this.tryRun(async () => {
712
- await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} DROP COLUMN ${(0, generator_1.quoteIdentifier)(column)} CASCADE;`);
713
- return { changes: 1 };
714
- });
715
- }
716
- async dropTable(table) {
717
- if (this.isReadOnly)
718
- return this.readonlyBlocked();
719
- return this.tryRun(async () => {
720
- await this.pool.query(`DROP TABLE ${(0, generator_1.quoteIdentifier)(table)} CASCADE;`);
721
- return { changes: 1 };
722
- });
723
- }
724
- async createIndex(table, index) {
725
- if (this.isReadOnly)
726
- return this.readonlyBlocked();
727
- return this.tryRun(async () => {
728
- const unique = index.unique ? 'UNIQUE ' : '';
729
- const cols = index.columns.map(generator_1.quoteIdentifier).join(', ');
730
- const name = index.name || `idx_${table}_${index.columns.join('_')}`;
731
- await this.pool.query(`CREATE ${unique}INDEX IF NOT EXISTS ${(0, generator_1.quoteIdentifier)(name)} ON ${(0, generator_1.quoteIdentifier)(table)} (${cols});`);
732
- return { changes: 1 };
733
- });
734
- }
735
- async dropIndex(indexName) {
736
- if (this.isReadOnly)
737
- return this.readonlyBlocked();
738
- return this.tryRun(async () => {
739
- await this.pool.query(`DROP INDEX IF EXISTS ${(0, generator_1.quoteIdentifier)(indexName)} CASCADE;`);
740
- return { changes: 1 };
741
- });
742
- }
743
- }
744
- exports.PostgresDatabase = PostgresDatabase;
745
- //# sourceMappingURL=postgres.js.map
1
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.PostgresDatabase=void 0;const pg_1=require("pg"),generator_1=require("../sql/generator"),common_1=require("../utils/common"),types_1=require("./types"),filters_1=require("./filters");exports.PostgresDatabase=class{pool;logger;path;isReadOnly;dialect="postgres";schema;constructor(connection,logger,options={}){this.logger=logger;const opts="string"==typeof connection?{connectionString:connection,...options}:{...connection,...options};this.isReadOnly=Boolean(opts.readonly),this.schema=opts.schema||"public";let sslConfig=opts.ssl;if(opts.connectionString)try{const u=new URL(opts.connectionString),sslMode=(u.searchParams.get("sslmode")||u.searchParams.get("ssl")||"").toLowerCase(),host=u.hostname.toLowerCase();"disable"===sslMode?sslConfig=!1:void 0===sslConfig&&("require"!==sslMode&&"prefer"!==sslMode&&"no-verify"!==sslMode&&("localhost"===host||"127.0.0.1"===host||"::1"===host||"0.0.0.0"===host)||(sslConfig={rejectUnauthorized:!1}))}catch{}else if(opts.host&&void 0===sslConfig){const host=opts.host.toLowerCase();"localhost"===host||"127.0.0.1"===host||"::1"===host||"0.0.0.0"===host||(sslConfig={rejectUnauthorized:!1})}const poolConfig={connectionString:opts.connectionString,host:opts.host,port:opts.port,database:opts.database,user:opts.user,password:opts.password,ssl:sslConfig,max:opts.max??10,connectionTimeoutMillis:1e4,idleTimeoutMillis:3e4,keepAlive:!0};this.path=opts.connectionString?this.sanitizeConnectionString(opts.connectionString):`${opts.user||"postgres"}@${opts.host||"localhost"}:${opts.port||5432}/${opts.database||"postgres"}`,this.pool=new pg_1.Pool(poolConfig),this.pool.on("error",err=>{this.logger.error(`Unexpected PostgreSQL client error: ${(0,common_1.errorMessage)(err)}`)}),this.isReadOnly||this.initSchema().catch(err=>{this.logger.warn(`Failed to initialize Postgres schema: ${(0,common_1.errorMessage)(err)}`)}),this.logger.info(`Initialized PostgreSQL pool for ${this.path}${this.isReadOnly?" (read-only)":""}`)}sanitizeConnectionString(conn){try{const url=new URL(conn);return url.password&&(url.password="****"),url.toString()}catch{return conn.replace(/:([^@]+)@/,":****@")}}readonlyBlocked(){return{success:!1,error:"Database is open in read-only mode — write operations are disabled."}}async close(){try{await this.pool.end()}catch(err){this.logger.warn(`Failed to close PostgreSQL pool: ${(0,common_1.errorMessage)(err)}`)}}async tryRun(fn){try{return{success:!0,data:await fn()}}catch(err){const msg=(0,common_1.errorMessage)(err);return this.logger.error(msg),{success:!1,error:msg}}}async transaction(fn){if(this.isReadOnly)return this.readonlyBlocked();const client=await this.pool.connect();try{await client.query("BEGIN");const res=await fn(client);return await client.query("COMMIT"),{success:!0,data:res}}catch(err){try{await client.query("ROLLBACK")}catch{}const msg=(0,common_1.errorMessage)(err);return this.logger.error(msg),{success:!1,error:msg}}finally{client.release()}}async initSchema(){const sql=`\n CREATE TABLE IF NOT EXISTS ${(0,generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} (\n id SERIAL PRIMARY KEY,\n name VARCHAR(255) NOT NULL UNIQUE,\n sql TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n );\n `;await this.pool.query(sql)}hasMultipleStatements(sql){const s=String(sql??"").trim();if(!s)return!1;let count=0,inString=!1;for(let i=0;i<s.length;i++){const ch=s[i];if("'"===ch){if(inString&&"'"===s[i+1]){i++;continue}inString=!inString}else if(";"===ch&&!inString){s.slice(i+1).trim().length>0&&count++}}return count>0}async all(sql,params=[]){return this.tryRun(async()=>{const pgSql=(0,filters_1.convertPlaceholdersToPostgres)(sql);return(await this.pool.query(pgSql,params)).rows})}async run(sql,params=[]){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{const pgSql=(0,filters_1.convertPlaceholdersToPostgres)(sql);return{changes:(await this.pool.query(pgSql,params)).rowCount??0,lastInsertRowid:null}})}async execResult(sql){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{const res=await this.pool.query(sql);return{changes:Array.isArray(res)?res.reduce((acc,r)=>acc+(r.rowCount??0),0):res.rowCount??void 0}})}async runWrite(sql){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{const res=await this.pool.query(sql);return{changes:Array.isArray(res)?res.reduce((acc,r)=>acc+(r.rowCount??0),0):res.rowCount??void 0}})}async listTables(){return this.tryRun(async()=>(await this.pool.query("SELECT table_name AS name\n FROM information_schema.tables\n WHERE table_schema = $1 AND table_type = 'BASE TABLE'\n ORDER BY table_name;",[this.schema])).rows.map(r=>({name:r.name})))}async getTableInfo(table){return this.tryRun(async()=>{const[colRes,pkRes,fkRes,idxRes]=await Promise.all([this.pool.query("SELECT\n ordinal_position AS cid,\n column_name AS name,\n data_type AS type,\n udt_name,\n CASE WHEN is_nullable = 'NO' THEN 1 ELSE 0 END AS notnull,\n column_default AS dflt_value\n FROM information_schema.columns\n WHERE table_schema = $1 AND table_name = $2\n ORDER BY ordinal_position;",[this.schema,table]),this.pool.query("SELECT kcu.column_name\n FROM information_schema.table_constraints tc\n JOIN information_schema.key_column_usage kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n WHERE tc.constraint_type = 'PRIMARY KEY'\n AND tc.table_schema = $1\n AND tc.table_name = $2\n ORDER BY kcu.ordinal_position;",[this.schema,table]),this.pool.query('SELECT\n row_number() OVER () AS id,\n kcu.position_in_unique_constraint AS seq,\n ccu.table_name AS table,\n kcu.column_name AS "from",\n ccu.column_name AS "to",\n rc.update_rule AS on_update,\n rc.delete_rule AS on_delete\n FROM information_schema.table_constraints AS tc\n JOIN information_schema.key_column_usage AS kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n JOIN information_schema.constraint_column_usage AS ccu\n ON ccu.constraint_name = tc.constraint_name\n AND ccu.table_schema = tc.table_schema\n JOIN information_schema.referential_constraints AS rc\n ON rc.constraint_name = tc.constraint_name\n AND rc.constraint_schema = tc.table_schema\n WHERE tc.constraint_type = \'FOREIGN KEY\'\n AND tc.table_schema = $1\n AND tc.table_name = $2;',[this.schema,table]),this.pool.query("SELECT\n i.relname AS name,\n ix.indisunique AS unique,\n pg_get_indexdef(ix.indexrelid) AS sql,\n ARRAY(\n SELECT pg_get_indexdef(ix.indexrelid, k + 1, true)\n FROM generate_subscripts(ix.indkey, 1) as k\n ORDER BY k\n ) AS columns\n FROM pg_index ix\n JOIN pg_class t ON t.oid = ix.indrelid\n JOIN pg_class i ON i.oid = ix.indexrelid\n JOIN pg_namespace n ON n.oid = t.relnamespace\n WHERE n.nspname = $1 AND t.relname = $2;",[this.schema,table])]),primaryKey=pkRes.rows.map(r=>r.column_name),columns=colRes.rows.map(c=>{const isPk=primaryKey.includes(c.name),displayType="USER-DEFINED"===c.type?c.udt_name:c.type;return{cid:c.cid,name:c.name,type:displayType.toUpperCase(),notnull:c.notnull,dflt_value:c.dflt_value,pk:isPk?primaryKey.indexOf(c.name)+1:0}}),indexes=idxRes.rows.map(ix=>({name:ix.name,unique:Boolean(ix.unique),partial:0,origin:"c",columns:Array.isArray(ix.columns)?ix.columns:[],sql:ix.sql})),colDefs=columns.map(c=>{let def=` ${(0,generator_1.quoteIdentifier)(c.name)} ${c.type}`;return c.notnull&&(def+=" NOT NULL"),c.dflt_value&&(def+=` DEFAULT ${c.dflt_value}`),def});primaryKey.length&&colDefs.push(` PRIMARY KEY (${primaryKey.map(generator_1.quoteIdentifier).join(", ")})`);const synthesizedSql=`CREATE TABLE ${(0,generator_1.quoteIdentifier)(table)} (\n${colDefs.join(",\n")}\n);`;return{table:table,columns:columns,foreignKeys:fkRes.rows.map(f=>({id:Number(f.id),seq:Number(f.seq??1),table:f.table,from:f.from,to:f.to,on_update:f.on_update,on_delete:f.on_delete})),primaryKey:primaryKey,indexes:indexes,sql:synthesizedSql}})}async getRowCount(table,filters){return this.tryRun(async()=>{let pgWhere="",params=[];if(filters&&Object.keys(filters).length>0){const{where:where,params:p}=(0,filters_1.buildFilterClause)(filters,Object.keys(filters));pgWhere=(0,filters_1.convertPlaceholdersToPostgres)(where),params=p}const sql=`SELECT COUNT(*) AS c FROM ${(0,generator_1.quoteIdentifier)(table)}${pgWhere}`,res=await this.pool.query(sql,params);return Number(res.rows[0]?.c??0)})}async getRows(table,opts={}){return this.tryRun(async()=>{const orderCol=opts.orderBy,orderDir="desc"===opts.orderDir?"DESC":"ASC",limit=opts.limit&&opts.limit>0?opts.limit:200,offset=((opts.page&&opts.page>0?opts.page:1)-1)*limit;let pgWhere="",filterParams=[];if(opts.filters&&Object.keys(opts.filters).length>0){const{where:where,params:params}=(0,filters_1.buildFilterClause)(opts.filters,Object.keys(opts.filters));pgWhere=(0,filters_1.convertPlaceholdersToPostgres)(where),filterParams=params}const nextParamIdx=filterParams.length+1;let sql=`SELECT * FROM ${(0,generator_1.quoteIdentifier)(table)}${pgWhere}`;orderCol&&(sql+=` ORDER BY ${(0,generator_1.quoteIdentifier)(orderCol)} ${orderDir}`),sql+=` LIMIT $${nextParamIdx} OFFSET $${nextParamIdx+1}`;const allParams=[...filterParams,limit,offset];return(await this.pool.query(sql,allParams)).rows})}async getAllRows(table){return this.tryRun(async()=>(await this.pool.query(`SELECT * FROM ${(0,generator_1.quoteIdentifier)(table)}`)).rows)}async getRow(table,where){return this.tryRun(async()=>{const conds=where.map((w,idx)=>`${(0,generator_1.quoteIdentifier)(w.column)} = $${idx+1}`),sql=`SELECT * FROM ${(0,generator_1.quoteIdentifier)(table)} WHERE ${conds.join(" AND ")} LIMIT 1`;return(await this.pool.query(sql,where.map(w=>w.value))).rows[0]??null})}async insertRow(table,fields){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{const cols=fields.map(f=>(0,generator_1.quoteIdentifier)(f.column)),placeholders=fields.map((_,idx)=>`$${idx+1}`),sql=`INSERT INTO ${(0,generator_1.quoteIdentifier)(table)} (${cols.join(", ")}) VALUES (${placeholders.join(", ")}) RETURNING *`;return{changes:(await this.pool.query(sql,fields.map(f=>f.value))).rowCount??1,lastInsertRowid:null}})}async insertRows(table,rows){return this.transaction(async client=>{let inserted=0;for(const fields of rows){if(!fields.length)continue;const cols=fields.map(f=>(0,generator_1.quoteIdentifier)(f.column)),placeholders=fields.map((_,idx)=>`$${idx+1}`),sql=`INSERT INTO ${(0,generator_1.quoteIdentifier)(table)} (${cols.join(", ")}) VALUES (${placeholders.join(", ")})`;await client.query(sql,fields.map(f=>f.value)),inserted++}return{inserted:inserted,skipped:rows.length-inserted}})}async updateRow(table,fields,where){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{const sets=fields.map((f,idx)=>`${(0,generator_1.quoteIdentifier)(f.column)} = $${idx+1}`),conds=where.map((w,idx)=>`${(0,generator_1.quoteIdentifier)(w.column)} = $${fields.length+idx+1}`),sql=`UPDATE ${(0,generator_1.quoteIdentifier)(table)} SET ${sets.join(", ")} WHERE ${conds.join(" AND ")}`;return{changes:(await this.pool.query(sql,[...fields.map(f=>f.value),...where.map(w=>w.value)])).rowCount??0,lastInsertRowid:null}})}async updateRows(table,rows){return this.transaction(async client=>{let updated=0;for(const{fields:fields,where:where}of rows){if(!fields.length||!where.length)continue;const sets=fields.map((f,idx)=>`${(0,generator_1.quoteIdentifier)(f.column)} = $${idx+1}`),conds=where.map((w,idx)=>`${(0,generator_1.quoteIdentifier)(w.column)} = $${fields.length+idx+1}`),sql=`UPDATE ${(0,generator_1.quoteIdentifier)(table)} SET ${sets.join(", ")} WHERE ${conds.join(" AND ")}`;updated+=(await client.query(sql,[...fields.map(f=>f.value),...where.map(w=>w.value)])).rowCount??0}return{updated:updated}})}async deleteRow(table,where){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{const conds=where.map((w,idx)=>`${(0,generator_1.quoteIdentifier)(w.column)} = $${idx+1}`),sql=`DELETE FROM ${(0,generator_1.quoteIdentifier)(table)} WHERE ${conds.join(" AND ")}`;return{changes:(await this.pool.query(sql,where.map(w=>w.value))).rowCount??0,lastInsertRowid:null}})}async deleteRows(table,rows){return this.transaction(async client=>{let deleted=0;for(const where of rows){if(!where.length)continue;const conds=where.map((w,idx)=>`${(0,generator_1.quoteIdentifier)(w.column)} = $${idx+1}`),sql=`DELETE FROM ${(0,generator_1.quoteIdentifier)(table)} WHERE ${conds.join(" AND ")}`;deleted+=(await client.query(sql,where.map(w=>w.value))).rowCount??0}return{deleted:deleted}})}async getRowsByPks(table,pks){return this.tryRun(async()=>{const groups=[],params=[];let paramIdx=1;for(const where of pks){if(!where.length)continue;const conds=where.map(w=>(params.push(w.value),`${(0,generator_1.quoteIdentifier)(w.column)} = $${paramIdx++}`));groups.push(conds.join(" AND "))}if(!groups.length)return[];const sql=`SELECT * FROM ${(0,generator_1.quoteIdentifier)(table)} WHERE ${groups.map(g=>`(${g})`).join(" OR ")}`;return(await this.pool.query(sql,params)).rows})}async getRowsByFk(table,column,value,limit=50){return this.tryRun(async()=>{const n=Math.max(1,Math.min(500,Number(limit)||50)),countRes=await this.pool.query(`SELECT COUNT(*) AS c FROM ${(0,generator_1.quoteIdentifier)(table)} WHERE ${(0,generator_1.quoteIdentifier)(column)} = $1`,[value]);return{rows:(await this.pool.query(`SELECT * FROM ${(0,generator_1.quoteIdentifier)(table)} WHERE ${(0,generator_1.quoteIdentifier)(column)} = $1 LIMIT $2`,[value,n])).rows,total:Number(countRes.rows[0]?.c??0)}})}async getCreateStatement(table){const info=await this.getTableInfo(table);return info.success&&info.data?{success:!0,data:info.data.sql}:{success:!1,error:info.error}}async listSavedQueries(){return this.tryRun(async()=>(await this.pool.query(`SELECT id, name, sql, created_at FROM ${(0,generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} ORDER BY name`)).rows)}async saveQuery(name,sql){return this.tryRun(async()=>{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]);return{changes:res.rowCount??1,lastInsertRowid:Number(res.rows[0]?.id??null)}})}async deleteSavedQuery(id){return this.tryRun(async()=>({changes:(await this.pool.query(`DELETE FROM ${(0,generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} WHERE id = $1`,[Number(id)])).rowCount??0,lastInsertRowid:null}))}async getSchema(table){return this.tryRun(async()=>{const infoR=await this.getTableInfo(table);if(!infoR.success||!infoR.data)throw new Error(infoR.error||`Table ${table} not found.`);const{columns:columns,foreignKeys:foreignKeys,indexes:indexes,primaryKey:primaryKey}=infoR.data,references=(await this.pool.query('SELECT\n tc.table_name AS table,\n kcu.column_name AS "from",\n ccu.column_name AS "to"\n FROM information_schema.table_constraints AS tc\n JOIN information_schema.key_column_usage AS kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n JOIN information_schema.constraint_column_usage AS ccu\n ON ccu.constraint_name = tc.constraint_name\n AND ccu.table_schema = tc.table_schema\n WHERE tc.constraint_type = \'FOREIGN KEY\'\n AND tc.table_schema = $1\n AND ccu.table_name = $2;',[this.schema,table])).rows,columnDetails=columns.map(col=>{const isPk=primaryKey.includes(col.name),fk=foreignKeys.find(f=>f.from===col.name)??null,matchingIndexes=indexes.filter(i=>i.columns.includes(col.name)),unique=matchingIndexes.some(i=>i.unique&&1===i.columns.length&&i.columns[0]===col.name),indexed=matchingIndexes.length>0,referencedBy=references.filter(r=>r.to===col.name).map(r=>({table:r.table,from:r.from})),dropBlockers=[];return isPk&&1===primaryKey.length&&dropBlockers.push("Primary key"),referencedBy.length>0&&dropBlockers.push(`Referenced by ${referencedBy.map(r=>`${r.table}.${r.from}`).join(", ")}`),{...col,unique:unique,indexed:indexed,referencedBy:referencedBy,fk:fk,canDrop:0===dropBlockers.length,dropBlockers:dropBlockers}});return{table:table,columns:columnDetails,foreignKeys:foreignKeys,indexes:indexes,references:references}})}async getReferencingTables(table){return this.tryRun(async()=>{const refRes=await this.pool.query('SELECT\n tc.table_name AS table,\n kcu.column_name AS "from",\n ccu.column_name AS "to"\n FROM information_schema.table_constraints AS tc\n JOIN information_schema.key_column_usage AS kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n JOIN information_schema.constraint_column_usage AS ccu\n ON ccu.constraint_name = tc.constraint_name\n AND ccu.table_schema = tc.table_schema\n WHERE tc.constraint_type = \'FOREIGN KEY\'\n AND tc.table_schema = $1\n AND ccu.table_name = $2;',[this.schema,table]),byTable=new Map;for(const r of refRes.rows)byTable.has(r.table)||byTable.set(r.table,[]),byTable.get(r.table).push({from:r.from,to:r.to});const results=[];for(const[tbl,refs]of byTable.entries())try{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 ")}`);results.push({table:tbl,refs:refs,refCount:Number(countRes.rows[0]?.c??0)})}catch{results.push({table:tbl,refs:refs,refCount:0})}return results})}async renameTable(oldName,newName){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>(await this.pool.query(`ALTER TABLE ${(0,generator_1.quoteIdentifier)(oldName)} RENAME TO ${(0,generator_1.quoteIdentifier)(newName)};`),{changes:1}))}async addColumn(table,col){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{let sql=`ALTER TABLE ${(0,generator_1.quoteIdentifier)(table)} ADD COLUMN ${(0,generator_1.quoteIdentifier)(col.name)} ${col.type}`;if(col.notNull&&(sql+=" NOT NULL"),void 0!==col.defaultValue&&null!==col.defaultValue&&""!==col.defaultValue&&(sql+=` DEFAULT ${col.defaultValue}`),col.foreignKey&&(sql+=` REFERENCES ${(0,generator_1.quoteIdentifier)(col.foreignKey.table)} (${(0,generator_1.quoteIdentifier)(col.foreignKey.column)})`),await this.pool.query(sql),col.unique){const idxName=`idx_${table}_${col.name}_unique`;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)});`)}return{changes:1}})}async modifyColumn(table,oldColName,newColDef){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{const newName=String(newColDef.name||oldColName).trim();oldColName!==newName&&await this.pool.query(`ALTER TABLE ${(0,generator_1.quoteIdentifier)(table)} RENAME COLUMN ${(0,generator_1.quoteIdentifier)(oldColName)} TO ${(0,generator_1.quoteIdentifier)(newName)};`);const colToModify=newName;if(newColDef.type&&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};`),void 0!==newColDef.notNull){const action=newColDef.notNull?"SET NOT NULL":"DROP NOT NULL";await this.pool.query(`ALTER TABLE ${(0,generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0,generator_1.quoteIdentifier)(colToModify)} ${action};`)}if(void 0!==newColDef.defaultValue&&(null===newColDef.defaultValue||""===newColDef.defaultValue?await this.pool.query(`ALTER TABLE ${(0,generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0,generator_1.quoteIdentifier)(colToModify)} DROP DEFAULT;`):await this.pool.query(`ALTER TABLE ${(0,generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0,generator_1.quoteIdentifier)(colToModify)} SET DEFAULT ${newColDef.defaultValue};`)),void 0!==newColDef.unique){const idxName=`idx_${table}_${colToModify}_unique`;newColDef.unique?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)});`):await this.pool.query(`DROP INDEX IF EXISTS ${(0,generator_1.quoteIdentifier)(idxName)};`)}return{changes:1}})}async renameColumn(table,oldName,newName){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>(await this.pool.query(`ALTER TABLE ${(0,generator_1.quoteIdentifier)(table)} RENAME COLUMN ${(0,generator_1.quoteIdentifier)(oldName)} TO ${(0,generator_1.quoteIdentifier)(newName)};`),{changes:1}))}async dropColumn(table,column){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>(await this.pool.query(`ALTER TABLE ${(0,generator_1.quoteIdentifier)(table)} DROP COLUMN ${(0,generator_1.quoteIdentifier)(column)} CASCADE;`),{changes:1}))}async dropTable(table){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>(await this.pool.query(`DROP TABLE ${(0,generator_1.quoteIdentifier)(table)} CASCADE;`),{changes:1}))}async createIndex(table,index){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{const unique=index.unique?"UNIQUE ":"",cols=index.columns.map(generator_1.quoteIdentifier).join(", "),name=index.name||`idx_${table}_${index.columns.join("_")}`;return await this.pool.query(`CREATE ${unique}INDEX IF NOT EXISTS ${(0,generator_1.quoteIdentifier)(name)} ON ${(0,generator_1.quoteIdentifier)(table)} (${cols});`),{changes:1}})}async dropIndex(indexName){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>(await this.pool.query(`DROP INDEX IF EXISTS ${(0,generator_1.quoteIdentifier)(indexName)} CASCADE;`),{changes:1}))}};