@indiekitai/pg-dash 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp.js CHANGED
@@ -13,7 +13,7 @@ async function getOverview(pool2) {
13
13
  try {
14
14
  const version = await client.query("SHOW server_version");
15
15
  const uptime = await client.query(
16
- "SELECT now() - pg_postmaster_start_time() AS uptime"
16
+ `SELECT to_char(now() - pg_postmaster_start_time(), 'DD "d" HH24 "h" MI "m"') AS uptime`
17
17
  );
18
18
  const dbSize = await client.query(
19
19
  "SELECT pg_size_pretty(pg_database_size(current_database())) AS size"
@@ -197,17 +197,28 @@ async function getActivity(pool2) {
197
197
  }
198
198
 
199
199
  // src/server/advisor.ts
200
- var SEVERITY_WEIGHT = { critical: 20, warning: 8, info: 3 };
200
+ import Database from "better-sqlite3";
201
+ import path from "path";
202
+ import os from "os";
203
+ import fs from "fs";
204
+ var SEVERITY_WEIGHT = { critical: 15, warning: 5, info: 1 };
205
+ var MAX_DEDUCTION = { critical: 60, warning: 30, info: 10 };
201
206
  function computeAdvisorScore(issues) {
202
207
  let score = 100;
208
+ const deductions = { critical: 0, warning: 0, info: 0 };
203
209
  const counts = { critical: 0, warning: 0, info: 0 };
204
210
  for (const issue of issues) {
205
211
  counts[issue.severity]++;
206
212
  const n = counts[issue.severity];
207
213
  const weight = SEVERITY_WEIGHT[issue.severity];
208
- if (n <= 5) score -= weight;
209
- else if (n <= 15) score -= weight * 0.5;
210
- else score -= weight * 0.25;
214
+ let penalty;
215
+ if (n <= 3) penalty = weight;
216
+ else if (n <= 10) penalty = weight * 0.5;
217
+ else penalty = weight * 0.25;
218
+ deductions[issue.severity] += penalty;
219
+ }
220
+ for (const sev of ["critical", "warning", "info"]) {
221
+ score -= Math.min(deductions[sev], MAX_DEDUCTION[sev]);
211
222
  }
212
223
  return Math.max(0, Math.min(100, Math.round(score)));
213
224
  }
@@ -231,7 +242,10 @@ function computeBreakdown(issues) {
231
242
  async function getAdvisorReport(pool2, longQueryThreshold2 = 5) {
232
243
  const client = await pool2.connect();
233
244
  const issues = [];
245
+ const skipped = [];
234
246
  try {
247
+ const versionResult = await client.query("SHOW server_version_num");
248
+ const pgVersion = parseInt(versionResult.rows[0].server_version_num);
235
249
  try {
236
250
  const r = await client.query(`
237
251
  SELECT schemaname, relname, seq_scan, seq_tup_read, n_live_tup,
@@ -256,6 +270,7 @@ CREATE INDEX CONCURRENTLY idx_${row.relname}_<column> ON ${row.schemaname}.${row
256
270
  }
257
271
  } catch (err) {
258
272
  console.error("[advisor] Error checking seq scans:", err.message);
273
+ skipped.push("seq scans: " + err.message);
259
274
  }
260
275
  try {
261
276
  const r = await client.query(`
@@ -284,6 +299,7 @@ CREATE INDEX CONCURRENTLY idx_${row.relname}_<column> ON ${row.schemaname}.${row
284
299
  }
285
300
  } catch (err) {
286
301
  console.error("[advisor] Error checking bloated indexes:", err.message);
302
+ skipped.push("bloated indexes: " + err.message);
287
303
  }
288
304
  try {
289
305
  const r = await client.query(`
@@ -309,6 +325,7 @@ CREATE INDEX CONCURRENTLY idx_${row.relname}_<column> ON ${row.schemaname}.${row
309
325
  }
310
326
  } catch (err) {
311
327
  console.error("[advisor] Error checking table bloat:", err.message);
328
+ skipped.push("table bloat: " + err.message);
312
329
  }
313
330
  try {
314
331
  const r = await client.query(`
@@ -338,6 +355,7 @@ SHOW shared_buffers;`,
338
355
  }
339
356
  } catch (err) {
340
357
  console.error("[advisor] Error checking cache efficiency:", err.message);
358
+ skipped.push("cache efficiency: " + err.message);
341
359
  }
342
360
  try {
343
361
  const extCheck = await client.query("SELECT 1 FROM pg_extension WHERE extname = 'pg_stat_statements'");
@@ -366,6 +384,7 @@ SHOW shared_buffers;`,
366
384
  }
367
385
  } catch (err) {
368
386
  console.error("[advisor] Error checking slow queries:", err.message);
387
+ skipped.push("slow queries: " + err.message);
369
388
  }
370
389
  try {
371
390
  const r = await client.query(`
@@ -391,6 +410,7 @@ SHOW shared_buffers;`,
391
410
  }
392
411
  } catch (err) {
393
412
  console.error("[advisor] Error checking vacuum overdue:", err.message);
413
+ skipped.push("vacuum overdue: " + err.message);
394
414
  }
395
415
  try {
396
416
  const r = await client.query(`
@@ -419,6 +439,7 @@ SHOW shared_buffers;`,
419
439
  }
420
440
  } catch (err) {
421
441
  console.error("[advisor] Error checking analyze overdue:", err.message);
442
+ skipped.push("analyze overdue: " + err.message);
422
443
  }
423
444
  try {
424
445
  const r = await client.query(`
@@ -454,6 +475,7 @@ SHOW shared_buffers;`,
454
475
  }
455
476
  } catch (err) {
456
477
  console.error("[advisor] Error checking xid wraparound:", err.message);
478
+ skipped.push("xid wraparound: " + err.message);
457
479
  }
458
480
  try {
459
481
  const r = await client.query(`
@@ -462,9 +484,9 @@ SHOW shared_buffers;`,
462
484
  extract(epoch from now() - state_change)::int AS idle_seconds
463
485
  FROM pg_stat_activity
464
486
  WHERE state IN ('idle', 'idle in transaction')
465
- AND now() - state_change > interval '${longQueryThreshold2} minutes'
487
+ AND now() - state_change > $1 * interval '1 minute'
466
488
  AND pid != pg_backend_pid()
467
- `);
489
+ `, [longQueryThreshold2]);
468
490
  for (const row of r.rows) {
469
491
  const isIdleTx = row.state === "idle in transaction";
470
492
  issues.push({
@@ -480,6 +502,7 @@ SHOW shared_buffers;`,
480
502
  }
481
503
  } catch (err) {
482
504
  console.error("[advisor] Error checking idle connections:", err.message);
505
+ skipped.push("idle connections: " + err.message);
483
506
  }
484
507
  try {
485
508
  const r = await client.query(`
@@ -505,6 +528,7 @@ SHOW shared_buffers;`,
505
528
  }
506
529
  } catch (err) {
507
530
  console.error("[advisor] Error checking missing primary keys:", err.message);
531
+ skipped.push("missing primary keys: " + err.message);
508
532
  }
509
533
  try {
510
534
  const r = await client.query(`
@@ -531,6 +555,7 @@ SHOW shared_buffers;`,
531
555
  }
532
556
  } catch (err) {
533
557
  console.error("[advisor] Error checking unused indexes:", err.message);
558
+ skipped.push("unused indexes: " + err.message);
534
559
  }
535
560
  try {
536
561
  const r = await client.query(`
@@ -556,6 +581,7 @@ DROP INDEX CONCURRENTLY ${row.indexes.slice(1).join(";\nDROP INDEX CONCURRENTLY
556
581
  }
557
582
  } catch (err) {
558
583
  console.error("[advisor] Error checking duplicate indexes:", err.message);
584
+ skipped.push("duplicate indexes: " + err.message);
559
585
  }
560
586
  try {
561
587
  const r = await client.query(`
@@ -586,6 +612,7 @@ DROP INDEX CONCURRENTLY ${row.indexes.slice(1).join(";\nDROP INDEX CONCURRENTLY
586
612
  }
587
613
  } catch (err) {
588
614
  console.error("[advisor] Error checking missing FK indexes:", err.message);
615
+ skipped.push("missing FK indexes: " + err.message);
589
616
  }
590
617
  try {
591
618
  const r = await client.query(`
@@ -621,6 +648,7 @@ DROP INDEX CONCURRENTLY ${row.indexes.slice(1).join(";\nDROP INDEX CONCURRENTLY
621
648
  }
622
649
  } catch (err) {
623
650
  console.error("[advisor] Error checking locks:", err.message);
651
+ skipped.push("locks: " + err.message);
624
652
  }
625
653
  try {
626
654
  const r = await client.query(`
@@ -644,13 +672,15 @@ SELECT * FROM pg_stat_replication;`,
644
672
  }
645
673
  } catch (err) {
646
674
  console.error("[advisor] Error checking replication lag:", err.message);
675
+ skipped.push("replication lag: " + err.message);
647
676
  }
648
677
  try {
678
+ const checkpointView = pgVersion >= 17e4 ? "pg_stat_checkpointer" : "pg_stat_bgwriter";
649
679
  const r = await client.query(`
650
680
  SELECT checkpoints_req, checkpoints_timed,
651
681
  CASE WHEN (checkpoints_req + checkpoints_timed) = 0 THEN 0
652
682
  ELSE round(checkpoints_req::numeric / (checkpoints_req + checkpoints_timed) * 100, 1) END AS req_pct
653
- FROM pg_stat_bgwriter
683
+ FROM ${checkpointView}
654
684
  `);
655
685
  const reqPct = parseFloat(r.rows[0]?.req_pct ?? "0");
656
686
  if (reqPct > 50) {
@@ -669,6 +699,7 @@ SELECT pg_reload_conf();`,
669
699
  }
670
700
  } catch (err) {
671
701
  console.error("[advisor] Error checking checkpoint frequency:", err.message);
702
+ skipped.push("checkpoint frequency: " + err.message);
672
703
  }
673
704
  try {
674
705
  const r = await client.query(`SELECT setting FROM pg_settings WHERE name = 'autovacuum'`);
@@ -687,6 +718,7 @@ SELECT pg_reload_conf();`,
687
718
  }
688
719
  } catch (err) {
689
720
  console.error("[advisor] Error checking autovacuum:", err.message);
721
+ skipped.push("autovacuum: " + err.message);
690
722
  }
691
723
  try {
692
724
  const sbRes = await client.query(`SELECT setting, unit FROM pg_settings WHERE name = 'shared_buffers'`);
@@ -710,6 +742,7 @@ SELECT pg_reload_conf();`,
710
742
  }
711
743
  } catch (err) {
712
744
  console.error("[advisor] Error checking shared_buffers:", err.message);
745
+ skipped.push("shared_buffers: " + err.message);
713
746
  }
714
747
  try {
715
748
  const r = await client.query(`SELECT setting, unit FROM pg_settings WHERE name = 'work_mem'`);
@@ -729,6 +762,7 @@ SELECT pg_reload_conf();`,
729
762
  }
730
763
  } catch (err) {
731
764
  console.error("[advisor] Error checking work_mem:", err.message);
765
+ skipped.push("work_mem: " + err.message);
732
766
  }
733
767
  try {
734
768
  const r = await client.query(`
@@ -754,6 +788,7 @@ SELECT pg_reload_conf();`,
754
788
  }
755
789
  } catch (err) {
756
790
  console.error("[advisor] Error checking superuser connections:", err.message);
791
+ skipped.push("superuser connections: " + err.message);
757
792
  }
758
793
  try {
759
794
  const r = await client.query(`SELECT setting FROM pg_settings WHERE name = 'ssl'`);
@@ -775,6 +810,7 @@ SELECT pg_reload_conf();`,
775
810
  }
776
811
  } catch (err) {
777
812
  console.error("[advisor] Error checking SSL check:", err.message);
813
+ skipped.push("SSL check: " + err.message);
778
814
  }
779
815
  try {
780
816
  const r = await client.query(`
@@ -798,18 +834,44 @@ SELECT pg_reload_conf();`,
798
834
  }
799
835
  } catch (err) {
800
836
  console.error("[advisor] Error checking trust auth:", err.message);
837
+ skipped.push("trust auth: " + err.message);
801
838
  }
802
- const score = computeAdvisorScore(issues);
839
+ const ignoredIds = getIgnoredIssues();
840
+ const ignoredSet = new Set(ignoredIds);
841
+ const activeIssues = issues.filter((i) => !ignoredSet.has(i.id));
842
+ const ignoredCount = issues.length - activeIssues.length;
843
+ const score = computeAdvisorScore(activeIssues);
803
844
  return {
804
845
  score,
805
846
  grade: gradeFromScore(score),
806
- issues,
807
- breakdown: computeBreakdown(issues)
847
+ issues: activeIssues,
848
+ breakdown: computeBreakdown(activeIssues),
849
+ skipped,
850
+ ignoredCount
808
851
  };
809
852
  } finally {
810
853
  client.release();
811
854
  }
812
855
  }
856
+ var _ignoredDb = null;
857
+ function getIgnoredDb() {
858
+ if (_ignoredDb) return _ignoredDb;
859
+ const dataDir2 = process.env.PG_DASH_DATA_DIR || path.join(os.homedir(), ".pg-dash");
860
+ fs.mkdirSync(dataDir2, { recursive: true });
861
+ const dbPath = path.join(dataDir2, "alerts.db");
862
+ _ignoredDb = new Database(dbPath);
863
+ _ignoredDb.pragma("journal_mode = WAL");
864
+ _ignoredDb.exec("CREATE TABLE IF NOT EXISTS ignored_issues (issue_id TEXT PRIMARY KEY, ignored_at INTEGER)");
865
+ return _ignoredDb;
866
+ }
867
+ function getIgnoredIssues() {
868
+ try {
869
+ const db = getIgnoredDb();
870
+ return db.prepare("SELECT issue_id FROM ignored_issues").all().map((r) => r.issue_id);
871
+ } catch {
872
+ return [];
873
+ }
874
+ }
813
875
  function isSafeFix(sql) {
814
876
  const trimmed = sql.trim();
815
877
  if (!trimmed) return false;
@@ -833,10 +895,10 @@ function isSafeFix(sql) {
833
895
  }
834
896
 
835
897
  // src/mcp.ts
836
- import Database from "better-sqlite3";
837
- import path from "path";
838
- import os from "os";
839
- import fs, { readFileSync } from "fs";
898
+ import Database2 from "better-sqlite3";
899
+ import path2 from "path";
900
+ import os2 from "os";
901
+ import fs2, { readFileSync } from "fs";
840
902
  var pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
841
903
  var connString = process.argv[2] || process.env.PG_DASH_CONNECTION_STRING;
842
904
  if (!connString) {
@@ -846,19 +908,19 @@ if (!connString) {
846
908
  }
847
909
  var pool = new Pool({ connectionString: connString });
848
910
  var longQueryThreshold = parseInt(process.env.PG_DASH_LONG_QUERY_THRESHOLD || "5", 10);
849
- var dataDir = process.env.PG_DASH_DATA_DIR || path.join(os.homedir(), ".pg-dash");
850
- fs.mkdirSync(dataDir, { recursive: true });
911
+ var dataDir = process.env.PG_DASH_DATA_DIR || path2.join(os2.homedir(), ".pg-dash");
912
+ fs2.mkdirSync(dataDir, { recursive: true });
851
913
  var schemaDb = null;
852
914
  var alertsDb = null;
853
915
  try {
854
- const schemaPath = path.join(dataDir, "schema.db");
855
- if (fs.existsSync(schemaPath)) schemaDb = new Database(schemaPath, { readonly: true });
916
+ const schemaPath = path2.join(dataDir, "schema.db");
917
+ if (fs2.existsSync(schemaPath)) schemaDb = new Database2(schemaPath, { readonly: true });
856
918
  } catch (err) {
857
919
  console.error("[mcp] Error:", err.message);
858
920
  }
859
921
  try {
860
- const alertsPath = path.join(dataDir, "alerts.db");
861
- if (fs.existsSync(alertsPath)) alertsDb = new Database(alertsPath, { readonly: true });
922
+ const alertsPath = path2.join(dataDir, "alerts.db");
923
+ if (fs2.existsSync(alertsPath)) alertsDb = new Database2(alertsPath, { readonly: true });
862
924
  } catch (err) {
863
925
  console.error("[mcp] Error:", err.message);
864
926
  }