postgresai 0.14.0-dev.74 → 0.14.0-dev.76

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 (32) hide show
  1. package/bin/postgres-ai.ts +312 -6
  2. package/dist/bin/postgres-ai.js +327 -21
  3. package/dist/sql/02.extensions.sql +8 -0
  4. package/dist/sql/{02.permissions.sql → 03.permissions.sql} +1 -0
  5. package/dist/sql/sql/02.extensions.sql +8 -0
  6. package/dist/sql/sql/{02.permissions.sql → 03.permissions.sql} +1 -0
  7. package/dist/sql/sql/uninit/01.helpers.sql +5 -0
  8. package/dist/sql/sql/uninit/02.permissions.sql +30 -0
  9. package/dist/sql/sql/uninit/03.role.sql +27 -0
  10. package/dist/sql/uninit/01.helpers.sql +5 -0
  11. package/dist/sql/uninit/02.permissions.sql +30 -0
  12. package/dist/sql/uninit/03.role.sql +27 -0
  13. package/lib/init.ts +109 -8
  14. package/lib/metrics-embedded.ts +1 -1
  15. package/lib/supabase.ts +2 -10
  16. package/package.json +1 -1
  17. package/sql/02.extensions.sql +8 -0
  18. package/sql/{02.permissions.sql → 03.permissions.sql} +1 -0
  19. package/sql/uninit/01.helpers.sql +5 -0
  20. package/sql/uninit/02.permissions.sql +30 -0
  21. package/sql/uninit/03.role.sql +27 -0
  22. package/test/init.test.ts +245 -11
  23. package/test/supabase.test.ts +0 -59
  24. /package/dist/sql/{03.optional_rds.sql → 04.optional_rds.sql} +0 -0
  25. /package/dist/sql/{04.optional_self_managed.sql → 05.optional_self_managed.sql} +0 -0
  26. /package/dist/sql/{05.helpers.sql → 06.helpers.sql} +0 -0
  27. /package/dist/sql/sql/{03.optional_rds.sql → 04.optional_rds.sql} +0 -0
  28. /package/dist/sql/sql/{04.optional_self_managed.sql → 05.optional_self_managed.sql} +0 -0
  29. /package/dist/sql/sql/{05.helpers.sql → 06.helpers.sql} +0 -0
  30. /package/sql/{03.optional_rds.sql → 04.optional_rds.sql} +0 -0
  31. /package/sql/{04.optional_self_managed.sql → 05.optional_self_managed.sql} +0 -0
  32. /package/sql/{05.helpers.sql → 06.helpers.sql} +0 -0
@@ -13064,7 +13064,7 @@ var {
13064
13064
  // package.json
13065
13065
  var package_default = {
13066
13066
  name: "postgresai",
13067
- version: "0.14.0-dev.74",
13067
+ version: "0.14.0-dev.76",
13068
13068
  description: "postgres_ai CLI",
13069
13069
  license: "Apache-2.0",
13070
13070
  private: false,
@@ -15887,7 +15887,7 @@ var Result = import_lib.default.Result;
15887
15887
  var TypeOverrides = import_lib.default.TypeOverrides;
15888
15888
  var defaults = import_lib.default.defaults;
15889
15889
  // package.json
15890
- var version = "0.14.0-dev.74";
15890
+ var version = "0.14.0-dev.76";
15891
15891
  var package_default2 = {
15892
15892
  name: "postgresai",
15893
15893
  version,
@@ -24961,7 +24961,11 @@ end $$;`;
24961
24961
  const roleSql = applyTemplate(loadSqlTemplate("01.role.sql"), { ...vars, ROLE_STMT: roleStmt });
24962
24962
  steps.push({ name: "01.role", sql: roleSql });
24963
24963
  }
24964
- let permissionsSql = applyTemplate(loadSqlTemplate("02.permissions.sql"), vars);
24964
+ steps.push({
24965
+ name: "02.extensions",
24966
+ sql: loadSqlTemplate("02.extensions.sql")
24967
+ });
24968
+ let permissionsSql = applyTemplate(loadSqlTemplate("03.permissions.sql"), vars);
24965
24969
  if (SKIP_ALTER_USER_PROVIDERS.includes(provider)) {
24966
24970
  permissionsSql = permissionsSql.split(`
24967
24971
  `).filter((line) => {
@@ -24973,21 +24977,21 @@ end $$;`;
24973
24977
  `);
24974
24978
  }
24975
24979
  steps.push({
24976
- name: "02.permissions",
24980
+ name: "03.permissions",
24977
24981
  sql: permissionsSql
24978
24982
  });
24979
24983
  steps.push({
24980
- name: "05.helpers",
24981
- sql: applyTemplate(loadSqlTemplate("05.helpers.sql"), vars)
24984
+ name: "06.helpers",
24985
+ sql: applyTemplate(loadSqlTemplate("06.helpers.sql"), vars)
24982
24986
  });
24983
24987
  if (params.includeOptionalPermissions) {
24984
24988
  steps.push({
24985
- name: "03.optional_rds",
24986
- sql: applyTemplate(loadSqlTemplate("03.optional_rds.sql"), vars),
24989
+ name: "04.optional_rds",
24990
+ sql: applyTemplate(loadSqlTemplate("04.optional_rds.sql"), vars),
24987
24991
  optional: true
24988
24992
  }, {
24989
- name: "04.optional_self_managed",
24990
- sql: applyTemplate(loadSqlTemplate("04.optional_self_managed.sql"), vars),
24993
+ name: "05.optional_self_managed",
24994
+ sql: applyTemplate(loadSqlTemplate("05.optional_self_managed.sql"), vars),
24991
24995
  optional: true
24992
24996
  });
24993
24997
  }
@@ -25055,6 +25059,62 @@ async function applyInitPlan(params) {
25055
25059
  }
25056
25060
  return { applied, skippedOptional };
25057
25061
  }
25062
+ async function buildUninitPlan(params) {
25063
+ const monitoringUser = params.monitoringUser || DEFAULT_MONITORING_USER;
25064
+ const database = params.database;
25065
+ const provider = params.provider ?? "self-managed";
25066
+ const dropRole = params.dropRole ?? true;
25067
+ const qRole = quoteIdent(monitoringUser);
25068
+ const qDb = quoteIdent(database);
25069
+ const qRoleLiteral = quoteLiteral(monitoringUser);
25070
+ const steps = [];
25071
+ const vars = {
25072
+ ROLE_IDENT: qRole,
25073
+ DB_IDENT: qDb,
25074
+ ROLE_LITERAL: qRoleLiteral
25075
+ };
25076
+ steps.push({
25077
+ name: "01.drop_helpers",
25078
+ sql: applyTemplate(loadSqlTemplate("uninit/01.helpers.sql"), vars)
25079
+ });
25080
+ steps.push({
25081
+ name: "02.revoke_permissions",
25082
+ sql: applyTemplate(loadSqlTemplate("uninit/02.permissions.sql"), vars)
25083
+ });
25084
+ if (dropRole && !SKIP_ROLE_CREATION_PROVIDERS.includes(provider)) {
25085
+ steps.push({
25086
+ name: "03.drop_role",
25087
+ sql: applyTemplate(loadSqlTemplate("uninit/03.role.sql"), vars)
25088
+ });
25089
+ }
25090
+ return { monitoringUser, database, steps, dropRole };
25091
+ }
25092
+ async function applyUninitPlan(params) {
25093
+ const applied = [];
25094
+ const errors3 = [];
25095
+ const executeStep = async (step) => {
25096
+ await params.client.query("begin;");
25097
+ try {
25098
+ await params.client.query(step.sql, step.params);
25099
+ await params.client.query("commit;");
25100
+ } catch (e) {
25101
+ try {
25102
+ await params.client.query("rollback;");
25103
+ } catch {}
25104
+ throw e;
25105
+ }
25106
+ };
25107
+ for (const step of params.plan.steps) {
25108
+ try {
25109
+ await executeStep(step);
25110
+ applied.push(step.name);
25111
+ } catch (e) {
25112
+ const msg = e instanceof Error ? e.message : String(e);
25113
+ errors3.push(`${step.name}: ${msg}`);
25114
+ }
25115
+ }
25116
+ return { applied, errors: errors3 };
25117
+ }
25058
25118
  async function verifyInitSetup(params) {
25059
25119
  await params.client.query("begin isolation level repeatable read;");
25060
25120
  try {
@@ -25314,10 +25374,6 @@ class SupabaseClient {
25314
25374
  }
25315
25375
  async function fetchPoolerDatabaseUrl(config2, username) {
25316
25376
  const url = `${SUPABASE_API_BASE}/v1/projects/${encodeURIComponent(config2.projectRef)}/config/database/pooler`;
25317
- const effectiveUsername = (() => {
25318
- const suffix = `.${config2.projectRef}`;
25319
- return username.endsWith(suffix) ? username : `${username}${suffix}`;
25320
- })();
25321
25377
  try {
25322
25378
  const response = await fetch(url, {
25323
25379
  method: "GET",
@@ -25332,13 +25388,13 @@ async function fetchPoolerDatabaseUrl(config2, username) {
25332
25388
  if (Array.isArray(data) && data.length > 0) {
25333
25389
  const pooler = data[0];
25334
25390
  if (pooler.db_host && pooler.db_port && pooler.db_name) {
25335
- return `postgresql://${effectiveUsername}@${pooler.db_host}:${pooler.db_port}/${pooler.db_name}`;
25391
+ return `postgresql://${username}@${pooler.db_host}:${pooler.db_port}/${pooler.db_name}`;
25336
25392
  }
25337
25393
  if (typeof pooler.connection_string === "string") {
25338
25394
  try {
25339
25395
  const connUrl = new URL(pooler.connection_string);
25340
25396
  const portPart = connUrl.port ? `:${connUrl.port}` : "";
25341
- return `postgresql://${effectiveUsername}@${connUrl.hostname}${portPart}${connUrl.pathname}`;
25397
+ return `postgresql://${username}@${connUrl.hostname}${portPart}${connUrl.pathname}`;
25342
25398
  } catch {
25343
25399
  return null;
25344
25400
  }
@@ -28233,12 +28289,16 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
28233
28289
  if (errAny.code === "42501") {
28234
28290
  if (failedStep === "01.role") {
28235
28291
  console.error(" Context: role creation/update requires CREATEROLE or superuser");
28236
- } else if (failedStep === "02.permissions") {
28292
+ } else if (failedStep === "03.permissions") {
28237
28293
  console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
28238
28294
  }
28239
28295
  console.error(" Fix: ensure your Supabase access token has sufficient permissions");
28240
28296
  console.error(" Tip: run with --print-sql to review the exact SQL plan");
28241
28297
  }
28298
+ if (errAny.code === "42P06" || message.includes("already exists") && failedStep === "03.permissions") {
28299
+ console.error(" Hint: postgres_ai schema or objects already exist from a previous setup.");
28300
+ console.error(" Fix: run 'postgresai unprepare-db <connection>' first to clean up, then retry prepare-db.");
28301
+ }
28242
28302
  }
28243
28303
  if (errAny && typeof errAny === "object" && typeof errAny.httpStatus === "number") {
28244
28304
  if (errAny.httpStatus === 401) {
@@ -28502,13 +28562,258 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
28502
28562
  if (errAny.code === "42501") {
28503
28563
  if (failedStep === "01.role") {
28504
28564
  console.error(" Context: role creation/update requires CREATEROLE or superuser");
28505
- } else if (failedStep === "02.permissions") {
28565
+ } else if (failedStep === "03.permissions") {
28506
28566
  console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
28507
28567
  }
28508
28568
  console.error(" Fix: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges)");
28509
28569
  console.error(" Fix: on managed Postgres, use the provider's admin/master user");
28510
28570
  console.error(" Tip: run with --print-sql to review the exact SQL plan");
28511
28571
  }
28572
+ if (errAny.code === "42P06" || message.includes("already exists") && failedStep === "03.permissions") {
28573
+ console.error(" Hint: postgres_ai schema or objects already exist from a previous setup.");
28574
+ console.error(" Fix: run 'postgresai unprepare-db <connection>' first to clean up, then retry prepare-db.");
28575
+ }
28576
+ if (errAny.code === "ECONNREFUSED") {
28577
+ console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
28578
+ }
28579
+ if (errAny.code === "ENOTFOUND") {
28580
+ console.error(" Hint: DNS resolution failed; double-check the host name");
28581
+ }
28582
+ if (errAny.code === "ETIMEDOUT") {
28583
+ console.error(" Hint: connection timed out; check network/firewall rules");
28584
+ }
28585
+ }
28586
+ process.exitCode = 1;
28587
+ }
28588
+ } finally {
28589
+ if (client) {
28590
+ try {
28591
+ await client.end();
28592
+ } catch {}
28593
+ }
28594
+ }
28595
+ });
28596
+ program2.command("unprepare-db [conn]").description("remove monitoring setup: drop monitoring user, views, schema, and revoke permissions").option("--db-url <url>", "PostgreSQL connection URL (admin) (deprecated; pass it as positional arg)").option("-h, --host <host>", "PostgreSQL host (psql-like)").option("-p, --port <port>", "PostgreSQL port (psql-like)").option("-U, --username <username>", "PostgreSQL user (psql-like)").option("-d, --dbname <dbname>", "PostgreSQL database name (psql-like)").option("--admin-password <password>", "Admin connection password (otherwise uses PGPASSWORD if set)").option("--monitoring-user <name>", "Monitoring role name to remove", DEFAULT_MONITORING_USER).option("--keep-role", "Keep the monitoring role (only revoke permissions and drop objects)", false).option("--provider <provider>", "Database provider (e.g., supabase). Affects which steps are executed.").option("--print-sql", "Print SQL plan and exit (no changes applied)", false).option("--force", "Skip confirmation prompt", false).option("--json", "Output result as JSON (machine-readable)", false).addHelpText("after", [
28597
+ "",
28598
+ "Examples:",
28599
+ " postgresai unprepare-db postgresql://admin@host:5432/dbname",
28600
+ ' postgresai unprepare-db "dbname=dbname host=host user=admin"',
28601
+ " postgresai unprepare-db -h host -p 5432 -U admin -d dbname",
28602
+ "",
28603
+ "Admin password:",
28604
+ " --admin-password <password> or PGPASSWORD=... (libpq standard)",
28605
+ "",
28606
+ "Keep role but remove objects/permissions:",
28607
+ " postgresai unprepare-db <conn> --keep-role",
28608
+ "",
28609
+ "Inspect SQL without applying changes:",
28610
+ " postgresai unprepare-db <conn> --print-sql",
28611
+ "",
28612
+ "Offline SQL plan (no DB connection):",
28613
+ " postgresai unprepare-db --print-sql",
28614
+ "",
28615
+ "Skip confirmation prompt:",
28616
+ " postgresai unprepare-db <conn> --force"
28617
+ ].join(`
28618
+ `)).action(async (conn, opts, cmd) => {
28619
+ const jsonOutput = opts.json;
28620
+ const outputJson = (data) => {
28621
+ console.log(JSON.stringify(data, null, 2));
28622
+ };
28623
+ const outputError = (error2) => {
28624
+ if (jsonOutput) {
28625
+ outputJson({
28626
+ success: false,
28627
+ error: error2
28628
+ });
28629
+ } else {
28630
+ console.error(`Error: unprepare-db: ${error2.message}`);
28631
+ if (error2.step)
28632
+ console.error(` Step: ${error2.step}`);
28633
+ if (error2.code)
28634
+ console.error(` Code: ${error2.code}`);
28635
+ if (error2.detail)
28636
+ console.error(` Detail: ${error2.detail}`);
28637
+ if (error2.hint)
28638
+ console.error(` Hint: ${error2.hint}`);
28639
+ }
28640
+ process.exitCode = 1;
28641
+ };
28642
+ const shouldPrintSql = !!opts.printSql;
28643
+ const dropRole = !opts.keepRole;
28644
+ const providerWarning = validateProvider(opts.provider);
28645
+ if (providerWarning) {
28646
+ console.warn(`\u26A0 ${providerWarning}`);
28647
+ }
28648
+ if (!conn && !opts.dbUrl && !opts.host && !opts.port && !opts.username && !opts.adminPassword) {
28649
+ if (shouldPrintSql) {
28650
+ const database = (opts.dbname ?? process.env.PGDATABASE ?? "postgres").trim();
28651
+ const plan = await buildUninitPlan({
28652
+ database,
28653
+ monitoringUser: opts.monitoringUser,
28654
+ dropRole,
28655
+ provider: opts.provider
28656
+ });
28657
+ console.log(`
28658
+ --- SQL plan (offline; not connected) ---`);
28659
+ console.log(`-- database: ${database}`);
28660
+ console.log(`-- monitoring user: ${opts.monitoringUser}`);
28661
+ console.log(`-- provider: ${opts.provider ?? "self-managed"}`);
28662
+ console.log(`-- drop role: ${dropRole}`);
28663
+ for (const step of plan.steps) {
28664
+ console.log(`
28665
+ -- ${step.name}`);
28666
+ console.log(step.sql);
28667
+ }
28668
+ console.log(`
28669
+ --- end SQL plan ---
28670
+ `);
28671
+ return;
28672
+ }
28673
+ }
28674
+ let adminConn;
28675
+ try {
28676
+ adminConn = resolveAdminConnection({
28677
+ conn,
28678
+ dbUrlFlag: opts.dbUrl,
28679
+ host: opts.host ?? process.env.PGHOST,
28680
+ port: opts.port ?? process.env.PGPORT,
28681
+ username: opts.username ?? process.env.PGUSER,
28682
+ dbname: opts.dbname ?? process.env.PGDATABASE,
28683
+ adminPassword: opts.adminPassword,
28684
+ envPassword: process.env.PGPASSWORD
28685
+ });
28686
+ } catch (e) {
28687
+ const msg = e instanceof Error ? e.message : String(e);
28688
+ if (jsonOutput) {
28689
+ outputError({ message: msg });
28690
+ } else {
28691
+ console.error(`Error: unprepare-db: ${msg}`);
28692
+ if (typeof msg === "string" && msg.startsWith("Connection is required.")) {
28693
+ console.error("");
28694
+ cmd.outputHelp({ error: true });
28695
+ }
28696
+ process.exitCode = 1;
28697
+ }
28698
+ return;
28699
+ }
28700
+ if (!jsonOutput) {
28701
+ console.log(`Connecting to: ${adminConn.display}`);
28702
+ console.log(`Monitoring user: ${opts.monitoringUser}`);
28703
+ console.log(`Drop role: ${dropRole}`);
28704
+ }
28705
+ if (!opts.force && !jsonOutput && !shouldPrintSql) {
28706
+ const answer = await new Promise((resolve6) => {
28707
+ const readline = getReadline();
28708
+ readline.question(`This will remove the monitoring setup for user "${opts.monitoringUser}"${dropRole ? " and drop the role" : ""}. Continue? [y/N] `, (ans) => resolve6(ans.trim().toLowerCase()));
28709
+ });
28710
+ if (answer !== "y" && answer !== "yes") {
28711
+ console.log("Aborted.");
28712
+ return;
28713
+ }
28714
+ }
28715
+ let client;
28716
+ try {
28717
+ const connResult = await connectWithSslFallback(Client, adminConn);
28718
+ client = connResult.client;
28719
+ const dbRes = await client.query("select current_database() as db");
28720
+ const database = dbRes.rows?.[0]?.db;
28721
+ if (typeof database !== "string" || !database) {
28722
+ throw new Error("Failed to resolve current database name");
28723
+ }
28724
+ const plan = await buildUninitPlan({
28725
+ database,
28726
+ monitoringUser: opts.monitoringUser,
28727
+ dropRole,
28728
+ provider: opts.provider
28729
+ });
28730
+ if (shouldPrintSql) {
28731
+ console.log(`
28732
+ --- SQL plan ---`);
28733
+ for (const step of plan.steps) {
28734
+ console.log(`
28735
+ -- ${step.name}`);
28736
+ console.log(step.sql);
28737
+ }
28738
+ console.log(`
28739
+ --- end SQL plan ---
28740
+ `);
28741
+ return;
28742
+ }
28743
+ const { applied, errors: errors3 } = await applyUninitPlan({ client, plan });
28744
+ if (jsonOutput) {
28745
+ outputJson({
28746
+ success: errors3.length === 0,
28747
+ action: "unprepare",
28748
+ database,
28749
+ monitoringUser: opts.monitoringUser,
28750
+ dropRole,
28751
+ applied,
28752
+ errors: errors3
28753
+ });
28754
+ if (errors3.length > 0) {
28755
+ process.exitCode = 1;
28756
+ }
28757
+ } else {
28758
+ if (errors3.length === 0) {
28759
+ console.log("\u2713 unprepare-db completed");
28760
+ console.log(`Applied ${applied.length} steps`);
28761
+ } else {
28762
+ console.log("\u26A0 unprepare-db completed with errors");
28763
+ console.log(`Applied ${applied.length} steps`);
28764
+ console.log("Errors:");
28765
+ for (const err of errors3) {
28766
+ console.log(` - ${err}`);
28767
+ }
28768
+ process.exitCode = 1;
28769
+ }
28770
+ }
28771
+ } catch (error2) {
28772
+ const errAny = error2;
28773
+ let message = "";
28774
+ if (error2 instanceof Error && error2.message) {
28775
+ message = error2.message;
28776
+ } else if (errAny && typeof errAny === "object" && typeof errAny.message === "string" && errAny.message) {
28777
+ message = errAny.message;
28778
+ } else {
28779
+ message = String(error2);
28780
+ }
28781
+ if (!message || message === "[object Object]") {
28782
+ message = "Unknown error";
28783
+ }
28784
+ const errorObj = { message };
28785
+ if (errAny && typeof errAny === "object") {
28786
+ if (typeof errAny.code === "string" && errAny.code)
28787
+ errorObj.code = errAny.code;
28788
+ if (typeof errAny.detail === "string" && errAny.detail)
28789
+ errorObj.detail = errAny.detail;
28790
+ if (typeof errAny.hint === "string" && errAny.hint)
28791
+ errorObj.hint = errAny.hint;
28792
+ }
28793
+ if (jsonOutput) {
28794
+ outputJson({
28795
+ success: false,
28796
+ error: errorObj
28797
+ });
28798
+ process.exitCode = 1;
28799
+ } else {
28800
+ console.error(`Error: unprepare-db: ${message}`);
28801
+ if (errAny && typeof errAny === "object") {
28802
+ if (typeof errAny.code === "string" && errAny.code) {
28803
+ console.error(` Code: ${errAny.code}`);
28804
+ }
28805
+ if (typeof errAny.detail === "string" && errAny.detail) {
28806
+ console.error(` Detail: ${errAny.detail}`);
28807
+ }
28808
+ if (typeof errAny.hint === "string" && errAny.hint) {
28809
+ console.error(` Hint: ${errAny.hint}`);
28810
+ }
28811
+ }
28812
+ if (errAny && typeof errAny === "object" && typeof errAny.code === "string") {
28813
+ if (errAny.code === "42501") {
28814
+ console.error(" Context: dropping roles/objects requires sufficient privileges");
28815
+ console.error(" Fix: connect as a superuser (or a role with appropriate DROP privileges)");
28816
+ }
28512
28817
  if (errAny.code === "ECONNREFUSED") {
28513
28818
  console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
28514
28819
  }
@@ -28527,6 +28832,7 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
28527
28832
  await client.end();
28528
28833
  } catch {}
28529
28834
  }
28835
+ closeReadline();
28530
28836
  }
28531
28837
  });
28532
28838
  program2.command("checkup [conn]").description("generate health check reports directly from PostgreSQL (express mode)").option("--check-id <id>", `specific check to run: ${Object.keys(CHECK_INFO).join(", ")}, or ALL`, "ALL").option("--node-name <name>", "node name for reports", "node-01").option("--output <path>", "output directory for JSON files").option("--[no-]upload", "upload JSON results to PostgresAI (default: enabled; requires API key)", undefined).option("--project <project>", "project name or ID for remote upload (used with --upload; defaults to config defaultProject; auto-generated on first run)").option("--json", "output JSON to stdout (implies --no-upload)").addHelpText("after", [
@@ -28655,14 +28961,14 @@ async function resolveOrInitPaths() {
28655
28961
  }
28656
28962
  function isDockerRunning() {
28657
28963
  try {
28658
- const result = spawnSync2("docker", ["info"], { stdio: "pipe" });
28964
+ const result = spawnSync2("docker", ["info"], { stdio: "pipe", timeout: 5000 });
28659
28965
  return result.status === 0;
28660
28966
  } catch {
28661
28967
  return false;
28662
28968
  }
28663
28969
  }
28664
28970
  function getComposeCmd() {
28665
- const tryCmd = (cmd, args) => spawnSync2(cmd, args, { stdio: "ignore" }).status === 0;
28971
+ const tryCmd = (cmd, args) => spawnSync2(cmd, args, { stdio: "ignore", timeout: 5000 }).status === 0;
28666
28972
  if (tryCmd("docker-compose", ["version"]))
28667
28973
  return ["docker-compose"];
28668
28974
  if (tryCmd("docker", ["compose", "version"]))
@@ -28671,7 +28977,7 @@ function getComposeCmd() {
28671
28977
  }
28672
28978
  function checkRunningContainers() {
28673
28979
  try {
28674
- const result = spawnSync2("docker", ["ps", "--filter", "name=grafana-with-datasources", "--filter", "name=pgwatch", "--format", "{{.Names}}"], { stdio: "pipe", encoding: "utf8" });
28980
+ const result = spawnSync2("docker", ["ps", "--filter", "name=grafana-with-datasources", "--filter", "name=pgwatch", "--format", "{{.Names}}"], { stdio: "pipe", encoding: "utf8", timeout: 5000 });
28675
28981
  if (result.status === 0 && result.stdout) {
28676
28982
  const containers = result.stdout.trim().split(`
28677
28983
  `).filter(Boolean);
@@ -0,0 +1,8 @@
1
+ -- Extensions required for postgres_ai monitoring
2
+
3
+ -- Enable pg_stat_statements for query performance monitoring
4
+ -- Note: Uses IF NOT EXISTS because extension may already be installed.
5
+ -- We do NOT drop this extension in unprepare-db since it may have been pre-existing.
6
+ create extension if not exists pg_stat_statements;
7
+
8
+
@@ -8,6 +8,7 @@ grant pg_monitor to {{ROLE_IDENT}};
8
8
  grant select on pg_catalog.pg_index to {{ROLE_IDENT}};
9
9
 
10
10
  -- Create postgres_ai schema for our objects
11
+ -- Using IF NOT EXISTS for idempotency - prepare-db can be run multiple times
11
12
  create schema if not exists postgres_ai;
12
13
  grant usage on schema postgres_ai to {{ROLE_IDENT}};
13
14
 
@@ -0,0 +1,8 @@
1
+ -- Extensions required for postgres_ai monitoring
2
+
3
+ -- Enable pg_stat_statements for query performance monitoring
4
+ -- Note: Uses IF NOT EXISTS because extension may already be installed.
5
+ -- We do NOT drop this extension in unprepare-db since it may have been pre-existing.
6
+ create extension if not exists pg_stat_statements;
7
+
8
+
@@ -8,6 +8,7 @@ grant pg_monitor to {{ROLE_IDENT}};
8
8
  grant select on pg_catalog.pg_index to {{ROLE_IDENT}};
9
9
 
10
10
  -- Create postgres_ai schema for our objects
11
+ -- Using IF NOT EXISTS for idempotency - prepare-db can be run multiple times
11
12
  create schema if not exists postgres_ai;
12
13
  grant usage on schema postgres_ai to {{ROLE_IDENT}};
13
14
 
@@ -0,0 +1,5 @@
1
+ -- Drop helper functions created by prepare-db (template-filled by cli/lib/init.ts)
2
+ -- Run before dropping the postgres_ai schema.
3
+
4
+ drop function if exists postgres_ai.explain_generic(text, text, text);
5
+ drop function if exists postgres_ai.table_describe(text);
@@ -0,0 +1,30 @@
1
+ -- Revoke permissions and drop objects created by prepare-db (template-filled by cli/lib/init.ts)
2
+
3
+ -- Drop the postgres_ai.pg_statistic view
4
+ drop view if exists postgres_ai.pg_statistic;
5
+
6
+ -- Drop the postgres_ai schema (CASCADE to handle any remaining objects)
7
+ drop schema if exists postgres_ai cascade;
8
+
9
+ -- Revoke permissions from the monitoring role
10
+ -- Use a DO block to handle the case where the role doesn't exist
11
+ do $$ begin
12
+ revoke pg_monitor from {{ROLE_IDENT}};
13
+ exception when undefined_object then
14
+ null; -- Role doesn't exist, nothing to revoke
15
+ end $$;
16
+
17
+ do $$ begin
18
+ revoke select on pg_catalog.pg_index from {{ROLE_IDENT}};
19
+ exception when undefined_object then
20
+ null; -- Role doesn't exist
21
+ end $$;
22
+
23
+ do $$ begin
24
+ revoke connect on database {{DB_IDENT}} from {{ROLE_IDENT}};
25
+ exception when undefined_object then
26
+ null; -- Role doesn't exist
27
+ end $$;
28
+
29
+ -- Note: USAGE on public is typically granted by default; we don't revoke it
30
+ -- to avoid breaking other applications that may rely on it.
@@ -0,0 +1,27 @@
1
+ -- Drop the monitoring role created by prepare-db (template-filled by cli/lib/init.ts)
2
+ -- This must run after revoking all permissions from the role.
3
+
4
+ -- Use a DO block to handle the case where the role doesn't exist
5
+ do $$ begin
6
+ -- Reassign owned objects to current user before dropping
7
+ -- This handles any objects that might have been created by the role
8
+ begin
9
+ execute format('reassign owned by %I to current_user', {{ROLE_LITERAL}});
10
+ exception when undefined_object then
11
+ null; -- Role doesn't exist, nothing to reassign
12
+ end;
13
+
14
+ -- Drop owned objects (in case reassign didn't work for some objects)
15
+ begin
16
+ execute format('drop owned by %I', {{ROLE_LITERAL}});
17
+ exception when undefined_object then
18
+ null; -- Role doesn't exist
19
+ end;
20
+
21
+ -- Drop the role
22
+ begin
23
+ execute format('drop role %I', {{ROLE_LITERAL}});
24
+ exception when undefined_object then
25
+ null; -- Role doesn't exist, that's fine
26
+ end;
27
+ end $$;
@@ -0,0 +1,5 @@
1
+ -- Drop helper functions created by prepare-db (template-filled by cli/lib/init.ts)
2
+ -- Run before dropping the postgres_ai schema.
3
+
4
+ drop function if exists postgres_ai.explain_generic(text, text, text);
5
+ drop function if exists postgres_ai.table_describe(text);
@@ -0,0 +1,30 @@
1
+ -- Revoke permissions and drop objects created by prepare-db (template-filled by cli/lib/init.ts)
2
+
3
+ -- Drop the postgres_ai.pg_statistic view
4
+ drop view if exists postgres_ai.pg_statistic;
5
+
6
+ -- Drop the postgres_ai schema (CASCADE to handle any remaining objects)
7
+ drop schema if exists postgres_ai cascade;
8
+
9
+ -- Revoke permissions from the monitoring role
10
+ -- Use a DO block to handle the case where the role doesn't exist
11
+ do $$ begin
12
+ revoke pg_monitor from {{ROLE_IDENT}};
13
+ exception when undefined_object then
14
+ null; -- Role doesn't exist, nothing to revoke
15
+ end $$;
16
+
17
+ do $$ begin
18
+ revoke select on pg_catalog.pg_index from {{ROLE_IDENT}};
19
+ exception when undefined_object then
20
+ null; -- Role doesn't exist
21
+ end $$;
22
+
23
+ do $$ begin
24
+ revoke connect on database {{DB_IDENT}} from {{ROLE_IDENT}};
25
+ exception when undefined_object then
26
+ null; -- Role doesn't exist
27
+ end $$;
28
+
29
+ -- Note: USAGE on public is typically granted by default; we don't revoke it
30
+ -- to avoid breaking other applications that may rely on it.
@@ -0,0 +1,27 @@
1
+ -- Drop the monitoring role created by prepare-db (template-filled by cli/lib/init.ts)
2
+ -- This must run after revoking all permissions from the role.
3
+
4
+ -- Use a DO block to handle the case where the role doesn't exist
5
+ do $$ begin
6
+ -- Reassign owned objects to current user before dropping
7
+ -- This handles any objects that might have been created by the role
8
+ begin
9
+ execute format('reassign owned by %I to current_user', {{ROLE_LITERAL}});
10
+ exception when undefined_object then
11
+ null; -- Role doesn't exist, nothing to reassign
12
+ end;
13
+
14
+ -- Drop owned objects (in case reassign didn't work for some objects)
15
+ begin
16
+ execute format('drop owned by %I', {{ROLE_LITERAL}});
17
+ exception when undefined_object then
18
+ null; -- Role doesn't exist
19
+ end;
20
+
21
+ -- Drop the role
22
+ begin
23
+ execute format('drop role %I', {{ROLE_LITERAL}});
24
+ exception when undefined_object then
25
+ null; -- Role doesn't exist, that's fine
26
+ end;
27
+ end $$;