admindb 1.2.1 → 2.1.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 (208) hide show
  1. package/README.md +262 -329
  2. package/dist/app.d.ts +25 -4
  3. package/dist/app.js +79 -15
  4. package/dist/app.js.map +1 -1
  5. package/dist/auth/config.d.ts +19 -0
  6. package/dist/auth/config.js +147 -0
  7. package/dist/auth/config.js.map +1 -0
  8. package/dist/auth/crypto.d.ts +27 -0
  9. package/dist/auth/crypto.js +133 -0
  10. package/dist/auth/crypto.js.map +1 -0
  11. package/dist/auth/index.d.ts +5 -0
  12. package/dist/auth/index.js +22 -0
  13. package/dist/auth/index.js.map +1 -0
  14. package/dist/auth/middleware.d.ts +14 -0
  15. package/dist/auth/middleware.js +100 -0
  16. package/dist/auth/middleware.js.map +1 -0
  17. package/dist/auth/routes.d.ts +7 -0
  18. package/dist/auth/routes.js +78 -0
  19. package/dist/auth/routes.js.map +1 -0
  20. package/dist/auth/types.d.ts +35 -0
  21. package/dist/auth/types.js +11 -0
  22. package/dist/auth/types.js.map +1 -0
  23. package/dist/cli/args.d.ts +45 -0
  24. package/dist/cli/args.js +328 -0
  25. package/dist/cli/args.js.map +1 -0
  26. package/dist/cli/config.d.ts +56 -0
  27. package/dist/cli/config.js +133 -0
  28. package/dist/cli/config.js.map +1 -0
  29. package/dist/cli/index.d.ts +4 -0
  30. package/dist/cli/index.js +21 -0
  31. package/dist/cli/index.js.map +1 -0
  32. package/dist/cli/runner.d.ts +1 -0
  33. package/dist/cli/runner.js +222 -0
  34. package/dist/cli/runner.js.map +1 -0
  35. package/dist/cli.js +2 -127
  36. package/dist/cli.js.map +1 -1
  37. package/dist/data/datasets.d.ts +17 -0
  38. package/dist/data/datasets.js +133 -0
  39. package/dist/data/datasets.js.map +1 -0
  40. package/dist/data/detector.d.ts +30 -0
  41. package/dist/data/detector.js +472 -0
  42. package/dist/data/detector.js.map +1 -0
  43. package/dist/data/engine.d.ts +10 -0
  44. package/dist/data/engine.js +211 -0
  45. package/dist/data/engine.js.map +1 -0
  46. package/dist/data/index.d.ts +5 -0
  47. package/dist/data/index.js +22 -0
  48. package/dist/data/index.js.map +1 -0
  49. package/dist/data/strategies.d.ts +30 -0
  50. package/dist/data/strategies.js +343 -0
  51. package/dist/data/strategies.js.map +1 -0
  52. package/dist/data/types.d.ts +73 -0
  53. package/dist/data/types.js +8 -0
  54. package/dist/data/types.js.map +1 -0
  55. package/dist/db/database.d.ts +14 -146
  56. package/dist/db/database.js +208 -461
  57. package/dist/db/database.js.map +1 -1
  58. package/dist/db/export.d.ts +2 -2
  59. package/dist/db/export.js +7 -2
  60. package/dist/db/export.js.map +1 -1
  61. package/dist/db/filters.d.ts +22 -0
  62. package/dist/db/filters.js +158 -0
  63. package/dist/db/filters.js.map +1 -0
  64. package/dist/db/index.d.ts +8 -0
  65. package/dist/db/index.js +25 -0
  66. package/dist/db/index.js.map +1 -0
  67. package/dist/db/introspection.d.ts +6 -0
  68. package/dist/db/introspection.js +172 -0
  69. package/dist/db/introspection.js.map +1 -0
  70. package/dist/db/manager.d.ts +32 -19
  71. package/dist/db/manager.js +203 -44
  72. package/dist/db/manager.js.map +1 -1
  73. package/dist/db/migrations.d.ts +5 -0
  74. package/dist/db/migrations.js +110 -0
  75. package/dist/db/migrations.js.map +1 -0
  76. package/dist/db/postgres.d.ts +85 -0
  77. package/dist/db/postgres.js +745 -0
  78. package/dist/db/postgres.js.map +1 -0
  79. package/dist/db/types.d.ts +206 -0
  80. package/dist/db/types.js +7 -0
  81. package/dist/db/types.js.map +1 -0
  82. package/dist/index.d.ts +9 -2
  83. package/dist/index.js +46 -4
  84. package/dist/index.js.map +1 -1
  85. package/dist/public/css/app.css +1 -1
  86. package/dist/public/css/input.css +74 -11
  87. package/dist/public/js/app.js +142 -0
  88. package/dist/public/js/browse.js +591 -104
  89. package/dist/public/js/databases.js +105 -13
  90. package/dist/public/js/designer.js +55 -2
  91. package/dist/public/js/forms.js +610 -28
  92. package/dist/public/js/icons.js +51 -0
  93. package/dist/public/js/inspector.js +638 -0
  94. package/dist/public/js/query.js +93 -3
  95. package/dist/public/js/schema.js +185 -52
  96. package/dist/public/js/seed.js +865 -152
  97. package/dist/routes/api/helpers.d.ts +42 -0
  98. package/dist/routes/api/helpers.js +133 -0
  99. package/dist/routes/api/helpers.js.map +1 -0
  100. package/dist/routes/api/import-export.d.ts +3 -0
  101. package/dist/routes/api/import-export.js +85 -0
  102. package/dist/routes/api/import-export.js.map +1 -0
  103. package/dist/routes/api/index.d.ts +4 -0
  104. package/dist/routes/api/index.js +37 -0
  105. package/dist/routes/api/index.js.map +1 -0
  106. package/dist/routes/api/query.d.ts +3 -0
  107. package/dist/routes/api/query.js +76 -0
  108. package/dist/routes/api/query.js.map +1 -0
  109. package/dist/routes/api/rows.d.ts +3 -0
  110. package/dist/routes/api/rows.js +373 -0
  111. package/dist/routes/api/rows.js.map +1 -0
  112. package/dist/routes/api/seed.d.ts +3 -0
  113. package/dist/routes/api/seed.js +82 -0
  114. package/dist/routes/api/seed.js.map +1 -0
  115. package/dist/routes/api/tables.d.ts +3 -0
  116. package/dist/routes/api/tables.js +160 -0
  117. package/dist/routes/api/tables.js.map +1 -0
  118. package/dist/routes/databases.d.ts +8 -11
  119. package/dist/routes/databases.js +100 -66
  120. package/dist/routes/databases.js.map +1 -1
  121. package/dist/routes/index.d.ts +3 -0
  122. package/dist/routes/index.js +20 -0
  123. package/dist/routes/index.js.map +1 -0
  124. package/dist/routes/pages.d.ts +3 -3
  125. package/dist/routes/pages.js +157 -64
  126. package/dist/routes/pages.js.map +1 -1
  127. package/dist/serverless.d.ts +40 -0
  128. package/dist/serverless.js +210 -0
  129. package/dist/serverless.js.map +1 -0
  130. package/dist/sql/generator.d.ts +7 -3
  131. package/dist/sql/generator.js +144 -7
  132. package/dist/sql/generator.js.map +1 -1
  133. package/dist/sql/index.d.ts +2 -0
  134. package/dist/sql/index.js +19 -0
  135. package/dist/sql/index.js.map +1 -0
  136. package/dist/types/api.d.ts +412 -0
  137. package/dist/types/api.js +9 -0
  138. package/dist/types/api.js.map +1 -0
  139. package/dist/utils/colors.d.ts +40 -0
  140. package/dist/utils/colors.js +64 -0
  141. package/dist/utils/colors.js.map +1 -0
  142. package/dist/{util.d.ts → utils/common.d.ts} +10 -6
  143. package/dist/{util.js → utils/common.js} +80 -13
  144. package/dist/utils/common.js.map +1 -0
  145. package/dist/utils/csv.js.map +1 -0
  146. package/dist/utils/datatype.d.ts +49 -0
  147. package/dist/utils/datatype.js +222 -0
  148. package/dist/utils/datatype.js.map +1 -0
  149. package/dist/utils/icons.d.ts +11 -0
  150. package/dist/utils/icons.js +72 -0
  151. package/dist/utils/icons.js.map +1 -0
  152. package/dist/utils/index.d.ts +4 -0
  153. package/dist/utils/index.js +21 -0
  154. package/dist/utils/index.js.map +1 -0
  155. package/dist/{logger.d.ts → utils/logger.d.ts} +1 -1
  156. package/dist/utils/logger.js +40 -0
  157. package/dist/utils/logger.js.map +1 -0
  158. package/dist/views/layouts/main.hbs +40 -11
  159. package/dist/views/pages/databases.hbs +182 -74
  160. package/dist/views/pages/designer.hbs +29 -11
  161. package/dist/views/pages/error.hbs +3 -3
  162. package/dist/views/pages/form.hbs +5 -3
  163. package/dist/views/pages/home.hbs +22 -13
  164. package/dist/views/pages/login.hbs +111 -0
  165. package/dist/views/pages/query.hbs +8 -6
  166. package/dist/views/pages/schema.hbs +41 -13
  167. package/dist/views/pages/seed.hbs +200 -54
  168. package/dist/views/pages/table.hbs +152 -72
  169. package/dist/views/partials/icon.hbs +2 -0
  170. package/dist/views/partials/navbar.hbs +69 -25
  171. package/dist/views/partials/sidebar.hbs +108 -94
  172. package/docs/API.md +144 -0
  173. package/docs/EXAMPLES.md +576 -0
  174. package/docs/SECURITY.md +265 -0
  175. package/package.json +15 -4
  176. package/dist/args.d.ts +0 -28
  177. package/dist/args.js +0 -187
  178. package/dist/args.js.map +0 -1
  179. package/dist/config.d.ts +0 -17
  180. package/dist/config.js +0 -23
  181. package/dist/config.js.map +0 -1
  182. package/dist/csv.js.map +0 -1
  183. package/dist/data/generator.d.ts +0 -86
  184. package/dist/data/generator.js +0 -1031
  185. package/dist/data/generator.js.map +0 -1
  186. package/dist/logger.js +0 -25
  187. package/dist/logger.js.map +0 -1
  188. package/dist/routes/api.d.ts +0 -9
  189. package/dist/routes/api.js +0 -848
  190. package/dist/routes/api.js.map +0 -1
  191. package/dist/test/classifier.test.d.ts +0 -1
  192. package/dist/test/classifier.test.js +0 -44
  193. package/dist/test/classifier.test.js.map +0 -1
  194. package/dist/test/csv.test.d.ts +0 -1
  195. package/dist/test/csv.test.js +0 -64
  196. package/dist/test/csv.test.js.map +0 -1
  197. package/dist/test/database.test.d.ts +0 -1
  198. package/dist/test/database.test.js +0 -402
  199. package/dist/test/database.test.js.map +0 -1
  200. package/dist/test/generator.test.d.ts +0 -1
  201. package/dist/test/generator.test.js +0 -132
  202. package/dist/test/generator.test.js.map +0 -1
  203. package/dist/test/manager.test.d.ts +0 -1
  204. package/dist/test/manager.test.js +0 -146
  205. package/dist/test/manager.test.js.map +0 -1
  206. package/dist/util.js.map +0 -1
  207. /package/dist/{csv.d.ts → utils/csv.d.ts} +0 -0
  208. /package/dist/{csv.js → utils/csv.js} +0 -0
@@ -0,0 +1,745 @@
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