@rebasepro/server-postgres 0.24.1-canary.gcb918fc → 0.25.0-canary.g70dad01

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 (142) hide show
  1. package/dist/{BranchService-Bsn2wfaj.js → BranchService-DO_3_CXt.js} +118 -18
  2. package/dist/BranchService-DO_3_CXt.js.map +1 -0
  3. package/dist/PostgresBackendDriver.d.ts +13 -0
  4. package/dist/PostgresBootstrapper.d.ts +10 -0
  5. package/dist/backup/backup-cli.d.ts +120 -2
  6. package/dist/backup/backup-service.d.ts +74 -1
  7. package/dist/backup/pg-binary.d.ts +2 -2
  8. package/dist/backup/pg-tools.d.ts +65 -0
  9. package/dist/backup-cli-Ap2WX2uW.js +647 -0
  10. package/dist/backup-cli-Ap2WX2uW.js.map +1 -0
  11. package/dist/{backup-service-BTBB7RXn.js → backup-service-DytMimsL.js} +414 -24
  12. package/dist/backup-service-DytMimsL.js.map +1 -0
  13. package/dist/{boot-ddl-DVpsFN6r.js → boot-ddl-D1lMCZLd.js} +3 -3
  14. package/dist/{boot-ddl-DVpsFN6r.js.map → boot-ddl-D1lMCZLd.js.map} +1 -1
  15. package/dist/branch-argv.d.ts +18 -6
  16. package/dist/branch-prune.d.ts +18 -0
  17. package/dist/cli-console-CQGsBzSY.js +262 -0
  18. package/dist/cli-console-CQGsBzSY.js.map +1 -0
  19. package/dist/cli-console.d.ts +11 -0
  20. package/dist/{cli-errors-CaI4ntyD.js → cli-errors-CJTKAKax.js} +499 -107
  21. package/dist/cli-errors-CJTKAKax.js.map +1 -0
  22. package/dist/cli-errors.d.ts +42 -5
  23. package/dist/{cli-flags-JNI-pwSf.js → cli-flags-D-b0qL9T.js} +71 -6
  24. package/dist/cli-flags-D-b0qL9T.js.map +1 -0
  25. package/dist/cli-flags.d.ts +21 -0
  26. package/dist/cli-helpers.d.ts +53 -24
  27. package/dist/{arg-Dni7MzLB.js → cli-prompt-egtNKkZl.js} +49 -3
  28. package/dist/cli-prompt-egtNKkZl.js.map +1 -0
  29. package/dist/cli-prompt.d.ts +19 -0
  30. package/dist/cli-scratch-database.d.ts +47 -8
  31. package/dist/cli.js +615 -100
  32. package/dist/cli.js.map +1 -1
  33. package/dist/collections/validate-relations.d.ts +8 -2
  34. package/dist/{column-plan-helpers-drZIsSNf.js → column-plan-helpers-D_V4oaZh.js} +2 -2
  35. package/dist/{column-plan-helpers-drZIsSNf.js.map → column-plan-helpers-D_V4oaZh.js.map} +1 -1
  36. package/dist/{connection-C9mT8bgf.js → connection-C938nR3E.js} +2 -2
  37. package/dist/{connection-C9mT8bgf.js.map → connection-C938nR3E.js.map} +1 -1
  38. package/dist/{databasePoolManager-BIuewidb.js → databasePoolManager-Yqdw14wY.js} +4 -4
  39. package/dist/{databasePoolManager-BIuewidb.js.map → databasePoolManager-Yqdw14wY.js.map} +1 -1
  40. package/dist/{deferred-index-build-DP1_SNFo.js → deferred-index-build-BiTqxqdl.js} +8 -8
  41. package/dist/{deferred-index-build-DP1_SNFo.js.map → deferred-index-build-BiTqxqdl.js.map} +1 -1
  42. package/dist/{doctor-C4WiftHm.js → doctor-N3J88BN_.js} +8 -8
  43. package/dist/doctor-N3J88BN_.js.map +1 -0
  44. package/dist/{ensure-collection-policies-CXpbeNag.js → ensure-collection-policies-CVP6kIPT.js} +125 -18
  45. package/dist/ensure-collection-policies-CVP6kIPT.js.map +1 -0
  46. package/dist/{ensure-collection-tables-BhsU6qUE.js → ensure-collection-tables-1WVNALOW.js} +10 -10
  47. package/dist/{ensure-collection-tables-BhsU6qUE.js.map → ensure-collection-tables-1WVNALOW.js.map} +1 -1
  48. package/dist/{ensure-tables-CVm_yvzb.js → ensure-tables-D-Nm2k3S.js} +5 -5
  49. package/dist/{ensure-tables-CVm_yvzb.js.map → ensure-tables-D-Nm2k3S.js.map} +1 -1
  50. package/dist/{enum-labels-kept-D8fI1MoX.js → enum-labels-kept-e_93v2Ic.js} +3 -3
  51. package/dist/{enum-labels-kept-D8fI1MoX.js.map → enum-labels-kept-e_93v2Ic.js.map} +1 -1
  52. package/dist/{generate-drizzle-schema-Bz52jH2h.js → generate-drizzle-schema-C3EAP9HE.js} +3 -3
  53. package/dist/{generate-drizzle-schema-Bz52jH2h.js.map → generate-drizzle-schema-C3EAP9HE.js.map} +1 -1
  54. package/dist/{generate-drizzle-schema-logic-B74WN37S.js → generate-drizzle-schema-logic-DA3zM8zc.js} +4 -4
  55. package/dist/{generate-drizzle-schema-logic-B74WN37S.js.map → generate-drizzle-schema-logic-DA3zM8zc.js.map} +1 -1
  56. package/dist/{generate-files-CdEckX6c.js → generate-files-ClmOUHXj.js} +6 -47
  57. package/dist/generate-files-ClmOUHXj.js.map +1 -0
  58. package/dist/generate-next-step-DCeS4LrJ.js +74 -0
  59. package/dist/generate-next-step-DCeS4LrJ.js.map +1 -0
  60. package/dist/{generate-postgres-ddl-logic-B64O2oZF.js → generate-postgres-ddl-logic-4eUP4t8w.js} +4 -4
  61. package/dist/{generate-postgres-ddl-logic-B64O2oZF.js.map → generate-postgres-ddl-logic-4eUP4t8w.js.map} +1 -1
  62. package/dist/{generated-schema-staleness-Di9c4ZxN.js → generated-schema-staleness-FlxM2i4i.js} +102 -12
  63. package/dist/{generated-schema-staleness-Di9c4ZxN.js.map → generated-schema-staleness-FlxM2i4i.js.map} +1 -1
  64. package/dist/index.es.js +11 -11
  65. package/dist/{introspect-db-logic-C-Di8dA9.js → introspect-db-logic-CI5CU_n0.js} +145 -9
  66. package/dist/introspect-db-logic-CI5CU_n0.js.map +1 -0
  67. package/dist/{introspect-db-queries-D6kAqReI.js → introspect-db-queries-DevUrzll.js} +41 -6
  68. package/dist/introspect-db-queries-DevUrzll.js.map +1 -0
  69. package/dist/{open-rebase-CbOur0TU.js → open-rebase-Cwnqo5LU.js} +116 -51
  70. package/dist/open-rebase-Cwnqo5LU.js.map +1 -0
  71. package/dist/open-rebase.d.ts +12 -4
  72. package/dist/{pg-error-utils-vJezlXdI.js → pg-error-utils-Cmo8wZPy.js} +19 -5
  73. package/dist/pg-error-utils-Cmo8wZPy.js.map +1 -0
  74. package/dist/{plan-schema-B1YF_tYq.js → plan-schema-CXOucCw-.js} +4 -4
  75. package/dist/{plan-schema-B1YF_tYq.js.map → plan-schema-CXOucCw-.js.map} +1 -1
  76. package/dist/{policy-drift-Dm8i-s1J.js → policy-drift-vm0BcvcE.js} +28 -14
  77. package/dist/{policy-drift-Dm8i-s1J.js.map → policy-drift-vm0BcvcE.js.map} +1 -1
  78. package/dist/{push-index-prebuild-Db4AQF4i.js → push-index-prebuild-SXRlWBjc.js} +4 -4
  79. package/dist/{push-index-prebuild-Db4AQF4i.js.map → push-index-prebuild-SXRlWBjc.js.map} +1 -1
  80. package/dist/{push-legacy-renames-BK6ilQq2.js → push-legacy-renames-B37yKDac.js} +4 -4
  81. package/dist/{push-legacy-renames-BK6ilQq2.js.map → push-legacy-renames-B37yKDac.js.map} +1 -1
  82. package/dist/push-policy-plan-jXzHAWSu.js +127 -0
  83. package/dist/push-policy-plan-jXzHAWSu.js.map +1 -0
  84. package/dist/{render-ddl-CtC3y9Ol.js → render-ddl-CQNwZdse.js} +10 -9
  85. package/dist/render-ddl-CQNwZdse.js.map +1 -0
  86. package/dist/{rls-bootstrap-sql-RQUljnJ1.js → rls-bootstrap-sql-Bd-hJk08.js} +3 -58
  87. package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +1 -0
  88. package/dist/{rls-enforcement-DvzqlaL1.js → rls-enforcement-D7P4-ZYX.js} +2 -2
  89. package/dist/{rls-enforcement-DvzqlaL1.js.map → rls-enforcement-D7P4-ZYX.js.map} +1 -1
  90. package/dist/{__vite-browser-external-BnuHet1e.js → rolldown-runtime-V7rFbMRe.js} +1 -8
  91. package/dist/schema/atlas-apply-report.d.ts +23 -0
  92. package/dist/schema/atlas-argv.d.ts +24 -1
  93. package/dist/schema/atlas-exclude.d.ts +42 -0
  94. package/dist/schema/atlas-revisions.d.ts +4 -0
  95. package/dist/schema/carved-out-migration.d.ts +5 -4
  96. package/dist/schema/destructive-sql.d.ts +23 -1
  97. package/dist/schema/doctor-cli.js +5 -5
  98. package/dist/schema/ensure-collection-policies.d.ts +74 -0
  99. package/dist/schema/generate-drizzle-schema.js +1 -1
  100. package/dist/schema/generate-next-step.d.ts +23 -0
  101. package/dist/schema/generate-postgres-ddl.js +2 -2
  102. package/dist/schema/generated-schema-diff.d.ts +7 -0
  103. package/dist/schema/generated-schema-staleness.d.ts +7 -1
  104. package/dist/schema/introspect-db-logic.d.ts +32 -1
  105. package/dist/schema/introspect-db-queries.d.ts +18 -5
  106. package/dist/schema/introspect-db.js +55 -53
  107. package/dist/schema/introspect-db.js.map +1 -1
  108. package/dist/schema/migrate-baseline.d.ts +51 -0
  109. package/dist/schema/plan/render-ddl.d.ts +2 -1
  110. package/dist/schema/push-policy-plan.d.ts +70 -0
  111. package/dist/schema/undeclarable-constraints.d.ts +59 -0
  112. package/dist/security/policy-drift.d.ts +5 -0
  113. package/dist/services/BranchService.d.ts +38 -0
  114. package/dist/{source-DBDbBTL4.js → source-Bv5KPeIx.js} +109 -4
  115. package/dist/source-Bv5KPeIx.js.map +1 -0
  116. package/dist/utils/connection-string.d.ts +47 -0
  117. package/dist/{vector-index-CMJKb3C9.js → vector-index-BFhyEaiz.js} +2 -2
  118. package/dist/{vector-index-CMJKb3C9.js.map → vector-index-BFhyEaiz.js.map} +1 -1
  119. package/dist/{websocket-BLGc5yhs.js → websocket-BT85Yv_G.js} +41 -13
  120. package/dist/websocket-BT85Yv_G.js.map +1 -0
  121. package/package.json +7 -7
  122. package/dist/BranchService-Bsn2wfaj.js.map +0 -1
  123. package/dist/__vite-browser-external-BnuHet1e.js.map +0 -1
  124. package/dist/arg-Dni7MzLB.js.map +0 -1
  125. package/dist/backup-cli-DnpJi6I7.js +0 -420
  126. package/dist/backup-cli-DnpJi6I7.js.map +0 -1
  127. package/dist/backup-service-BTBB7RXn.js.map +0 -1
  128. package/dist/cli-console-Ca6ZlAVU.js +0 -154
  129. package/dist/cli-console-Ca6ZlAVU.js.map +0 -1
  130. package/dist/cli-errors-CaI4ntyD.js.map +0 -1
  131. package/dist/cli-flags-JNI-pwSf.js.map +0 -1
  132. package/dist/doctor-C4WiftHm.js.map +0 -1
  133. package/dist/ensure-collection-policies-CXpbeNag.js.map +0 -1
  134. package/dist/generate-files-CdEckX6c.js.map +0 -1
  135. package/dist/introspect-db-logic-C-Di8dA9.js.map +0 -1
  136. package/dist/introspect-db-queries-D6kAqReI.js.map +0 -1
  137. package/dist/open-rebase-CbOur0TU.js.map +0 -1
  138. package/dist/pg-error-utils-vJezlXdI.js.map +0 -1
  139. package/dist/render-ddl-CtC3y9Ol.js.map +0 -1
  140. package/dist/rls-bootstrap-sql-RQUljnJ1.js.map +0 -1
  141. package/dist/source-DBDbBTL4.js.map +0 -1
  142. package/dist/websocket-BLGc5yhs.js.map +0 -1
@@ -1,8 +1,8 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  __createRequire(import.meta.url);
3
- import { r as __exportAll } from "./__vite-browser-external-BnuHet1e.js";
4
- import { F as resolveLocalBin } from "./cli-errors-CaI4ntyD.js";
5
- import { o as execa, s as forLibpq } from "./rls-bootstrap-sql-RQUljnJ1.js";
3
+ import { n as __exportAll } from "./rolldown-runtime-V7rFbMRe.js";
4
+ import { Q as forLibpq, R as resolveLocalBin, et as splitPassword } from "./cli-errors-CJTKAKax.js";
5
+ import { o as execa } from "./rls-bootstrap-sql-Bd-hJk08.js";
6
6
  import path from "path";
7
7
  import fs from "fs";
8
8
  import os from "os";
@@ -235,6 +235,111 @@ function buildPgRestoreArgs(opts) {
235
235
  function buildPgRestoreListArgs(inputFile) {
236
236
  return ["--list", inputFile];
237
237
  }
238
+ /** `<id>; <catalog oid> <oid> <description> <namespace> <tag…> <owner>`, the shape of a TOC entry. */
239
+ var TOC_ENTRY = /^\d+;\s+\d+\s+\d+\s+(SCHEMA|TABLE)\s+(?!DATA\s)(\S+)\s+(.+)\s+(\S+)$/;
240
+ /** A TOC entry for a relation of any kind. `TABLE DATA`, `SEQUENCE SET` and `SEQUENCE OWNED BY` are not relations. */
241
+ var TOC_RELATION = /^\d+;\s+\d+\s+\d+\s+(?:FOREIGN TABLE|MATERIALIZED VIEW|TABLE|VIEW|SEQUENCE)\s+(?!DATA\s|SET\s|OWNED BY\s)(\S+)\s+(.+)\s+(\S+)$/;
242
+ /**
243
+ * The schemas and tables in `pg_restore --list` output: the objects a
244
+ * restore without `--clean` fails on when the target already has them.
245
+ *
246
+ * A TOC line ends in the object's owner, after its namespace and its name;
247
+ * a schema's namespace is `-`. The name is what lies between the two, so a
248
+ * name with a space in it (`Odd Name`) reads whole. `TABLE DATA` and every
249
+ * other kind of entry are not objects of their own here.
250
+ */
251
+ function parseDumpObjects(list) {
252
+ const objects = {
253
+ schemas: [],
254
+ tables: [],
255
+ relations: []
256
+ };
257
+ for (const line of list.split("\n")) {
258
+ const trimmed = line.trim();
259
+ const relation = TOC_RELATION.exec(trimmed);
260
+ if (relation) objects.relations.push({
261
+ schema: relation[1],
262
+ name: relation[2]
263
+ });
264
+ const entry = TOC_ENTRY.exec(trimmed);
265
+ if (!entry) continue;
266
+ const [, kind, namespace, name] = entry;
267
+ if (kind === "SCHEMA") objects.schemas.push(name);
268
+ else objects.tables.push({
269
+ schema: namespace,
270
+ table: name
271
+ });
272
+ }
273
+ return objects;
274
+ }
275
+ /** A Postgres identifier, double-quoted. */
276
+ function quoteIdentifier(name) {
277
+ return `"${name.replace(/"/g, "\"\"")}"`;
278
+ }
279
+ /**
280
+ * How long `restore --clean` waits for a lock before it gives up.
281
+ *
282
+ * A `rebase dev` serving from the database holds connections, and one inside a
283
+ * transaction would otherwise make the restore wait without a word. The dump's
284
+ * own script sets `lock_timeout = 0` after this, for the objects it creates.
285
+ */
286
+ var CLEAN_LOCK_TIMEOUT = "15s";
287
+ /**
288
+ * What `restore --clean` runs first, in the transaction the restore runs in:
289
+ * every schema the dump creates, dropped with `CASCADE`, and every table it
290
+ * creates in a schema it does not (`public`), the same.
291
+ *
292
+ * `CASCADE`, which `pg_restore --clean` never uses: a table the backup lacks
293
+ * in one of its schemas — `rebase.atlas_schema_revisions` from `rebase db
294
+ * migrate`, a table a newer runtime added — is exactly what stopped its plain
295
+ * `DROP SCHEMA rebase`, and a table that references one of the backup's is what
296
+ * stops a plain `DROP TABLE`. The dump's script follows, with its own `DROP …
297
+ * IF EXISTS` for everything else it creates.
298
+ */
299
+ function cleanRestorePrelude(objects) {
300
+ const schemas = objects.schemas.filter((schema) => schema !== "public");
301
+ const dropped = new Set(schemas);
302
+ const tables = objects.tables.filter((table) => !dropped.has(table.schema));
303
+ return [
304
+ "SET client_min_messages = warning",
305
+ `SET lock_timeout = '15s'`,
306
+ ...schemas.map((schema) => `DROP SCHEMA IF EXISTS ${quoteIdentifier(schema)} CASCADE`),
307
+ ...tables.map((table) => `DROP TABLE IF EXISTS ${quoteIdentifier(table.schema)}.${quoteIdentifier(table.table)} CASCADE`)
308
+ ];
309
+ }
310
+ /**
311
+ * `pg_restore` writing the dump as a SQL script on stdout, with the `DROP …
312
+ * IF EXISTS` that `--clean` adds, for `psql` to run. See {@link buildPsqlScriptArgs}.
313
+ */
314
+ function buildPgRestoreScriptArgs(opts) {
315
+ return [
316
+ "--format=custom",
317
+ "--clean",
318
+ "--if-exists",
319
+ ...opts.noOwner ? ["--no-owner"] : [],
320
+ "--file=-",
321
+ opts.inputFile
322
+ ];
323
+ }
324
+ /**
325
+ * `psql` running a script read from stdin, stopping at its first error.
326
+ *
327
+ * `-X`, so no `.psqlrc` changes what runs; `-q`, so it prints errors and
328
+ * warnings, not a command tag per statement. The transaction is the script's
329
+ * own: `restore --clean` writes the `BEGIN`, and the `COMMIT` only once
330
+ * `pg_restore` has written the whole dump.
331
+ */
332
+ function buildPsqlScriptArgs(connectionString) {
333
+ return [
334
+ "-X",
335
+ "-q",
336
+ "--no-password",
337
+ "-v",
338
+ "ON_ERROR_STOP=1",
339
+ `--dbname=${forLibpq(connectionString)}`,
340
+ "--file=-"
341
+ ];
342
+ }
238
343
  /**
239
344
  * Assemble the `pg_dumpall --globals-only` argument vector. Roles (and other
240
345
  * cluster-wide objects) live outside any single database, so a per-database
@@ -331,13 +436,14 @@ function selectBackupsToPrune(backups, options) {
331
436
  var OVERRIDE_VARIABLE = {
332
437
  pg_dump: "PG_DUMP_PATH",
333
438
  pg_restore: "PG_RESTORE_PATH",
334
- pg_dumpall: "PG_DUMPALL_PATH"
439
+ pg_dumpall: "PG_DUMPALL_PATH",
440
+ psql: "PSQL_PATH"
335
441
  };
336
442
  /** The variable that points at `tool` when it is not on the PATH. */
337
443
  function pgToolVariable(tool) {
338
444
  return OVERRIDE_VARIABLE[tool];
339
445
  }
340
- /** Locate `pg_dump` / `pg_restore` / `pg_dumpall`, honouring an env override. */
446
+ /** Locate `pg_dump` / `pg_restore` / `pg_dumpall` / `psql`, honouring an env override. */
341
447
  function resolvePgBinary(tool, env = process.env) {
342
448
  const override = env[OVERRIDE_VARIABLE[tool]];
343
449
  if (override && fs.existsSync(override)) return override;
@@ -432,21 +538,45 @@ function discardPartialDumpWith(exists, remove, file) {
432
538
  */
433
539
  var backup_service_exports = /* @__PURE__ */ __exportAll({
434
540
  BackupToolError: () => BackupToolError,
541
+ CleanRestoreFailed: () => CleanRestoreFailed,
435
542
  applyGlobals: () => applyGlobals,
436
543
  createDump: () => createDump,
437
544
  detectToolMajor: () => detectToolMajor,
438
545
  discardPartialDump: () => discardPartialDump,
439
546
  ensureDatabaseExists: () => ensureDatabaseExists,
547
+ findCleanExtras: () => findCleanExtras,
548
+ findRestoreConflicts: () => findRestoreConflicts,
440
549
  getServerVersionMajor: () => getServerVersionMajor,
441
550
  listBackups: () => listBackups,
442
551
  preflight: () => preflight,
443
552
  pruneBackups: () => pruneBackups,
553
+ readDumpObjects: () => readDumpObjects,
554
+ resetSharedSession: () => resetSharedSession,
444
555
  resolvePgBinary: () => resolvePgBinary,
445
556
  restoreDump: () => restoreDump,
557
+ restoreDumpReplacing: () => restoreDumpReplacing,
446
558
  uploadBackup: () => uploadBackup,
447
559
  validateDump: () => validateDump
448
560
  });
449
561
  /**
562
+ * How a libpq tool is invoked for a connection string: the URL its command
563
+ * line carries, and the environment it runs in.
564
+ *
565
+ * The password goes in `PGPASSWORD`, never on the command line, where every
566
+ * process listing could read it and every failure printed it back (execa's
567
+ * error repeats the command). See {@link splitPassword}.
568
+ */
569
+ function libpqInvocation(connectionString, env) {
570
+ const split = splitPassword(connectionString);
571
+ const toolEnv = {};
572
+ for (const [key, value] of Object.entries(env)) if (value !== void 0) toolEnv[key] = value;
573
+ if (split.password !== null) toolEnv.PGPASSWORD = split.password;
574
+ return {
575
+ connectionString: split.connectionString,
576
+ env: toolEnv
577
+ };
578
+ }
579
+ /**
450
580
  * Remove a dump artifact abandoned by a failed tool run. See
451
581
  * {@link discardPartialDumpWith} for why this exists and why the logic lives
452
582
  * in the execa-free module.
@@ -483,6 +613,43 @@ async function getServerVersionMajor(connectionString) {
483
613
  await client.end();
484
614
  }
485
615
  }
616
+ /** PGlite names itself in `version()`: `PostgreSQL 18.3 (PGlite 0.5.6) on wasm32-…`. */
617
+ var SHARED_SESSION_ENGINE = /\(PGlite\b/i;
618
+ /**
619
+ * Put back what a client tool left set on a database that is one session
620
+ * every connection shares, and do nothing on any other.
621
+ *
622
+ * The managed development database is PGlite: one session behind a socket
623
+ * server, so what `pg_dump` sets for itself — `search_path` emptied,
624
+ * `row_security` off, `PREPARE dumpEnumType …` — stays set for every client
625
+ * after it, a running `rebase dev` included. One `rebase db backup` there left
626
+ * every API request answering 500 "table does not exist", a restart did not
627
+ * help (the database outlives `rebase dev`), and the next backup failed on
628
+ * `prepared statement "dumpenumtype" already exists`.
629
+ *
630
+ * `RESET ALL` and `DEALLOCATE ALL`, not `DISCARD ALL`: that would also drop
631
+ * the `LISTEN` a running `rebase dev`'s realtime holds on the same session.
632
+ * Best effort: it runs after the tool, whose own outcome is what the caller
633
+ * reports.
634
+ */
635
+ async function resetSharedSession(connectionString) {
636
+ const { Client } = await import("pg");
637
+ const client = new Client({
638
+ connectionString,
639
+ connectionTimeoutMillis: 1e4,
640
+ query_timeout: 15e3
641
+ });
642
+ client.on("error", () => void 0);
643
+ try {
644
+ await client.connect();
645
+ const { rows } = await client.query("SELECT version() AS version");
646
+ if (!SHARED_SESSION_ENGINE.test(rows[0]?.version ?? "")) return;
647
+ await client.query("RESET ALL");
648
+ await client.query("DEALLOCATE ALL");
649
+ } catch {} finally {
650
+ await client.end().catch(() => void 0);
651
+ }
652
+ }
486
653
  /**
487
654
  * Verify the requested client tool exists and its major version is
488
655
  * compatible with the live server. Throws {@link BackupToolError} — with a
@@ -517,20 +684,26 @@ async function createDump(opts) {
517
684
  const outDir = opts.outDir ?? fs.mkdtempSync(path.join(os.tmpdir(), "rebase-backup-"));
518
685
  fs.mkdirSync(outDir, { recursive: true });
519
686
  const localFile = path.join(outDir, fileName);
687
+ createOwnerOnly(localFile);
688
+ const tool = libpqInvocation(opts.connectionString, env);
520
689
  const args = buildPgDumpArgs({
521
- connectionString: opts.connectionString,
690
+ connectionString: tool.connectionString,
522
691
  outFile: localFile,
523
692
  excludeSchemas: opts.excludeSchemas,
524
693
  noOwner: opts.noOwner,
525
694
  rowSecurity: opts.rowSecurity
526
695
  });
527
- const dumpEnv = { ...env };
696
+ const dumpEnv = { ...tool.env };
528
697
  if (opts.rowSecurity) dumpEnv.PGOPTIONS = [env.PGOPTIONS, buildRowSecurityPgOptions(opts.rowSecurity)].filter(Boolean).join(" ");
529
698
  try {
530
- await execa(bin, args, {
531
- stdio: opts.inheritStdio ? "inherit" : "pipe",
532
- env: dumpEnv
533
- });
699
+ try {
700
+ await execa(bin, args, {
701
+ stdio: opts.inheritStdio ? "inherit" : "pipe",
702
+ env: dumpEnv
703
+ });
704
+ } finally {
705
+ await resetSharedSession(opts.connectionString);
706
+ }
534
707
  } catch (error) {
535
708
  discardPartialDump(localFile);
536
709
  const diagnosis = diagnoseRowSecurityDumpFailure(error);
@@ -546,14 +719,19 @@ async function createDump(opts) {
546
719
  const dumpallBin = resolvePgBinary("pg_dumpall", env);
547
720
  if (!dumpallBin) throw new BackupToolError("Could not find the 'pg_dumpall' binary needed to capture cluster roles.", "Install the PostgreSQL client tools or set PG_DUMPALL_PATH. To take a role-incomplete backup anyway, pass includeGlobals: false.");
548
721
  const globalsFile = globalsFileForDump(localFile);
722
+ createOwnerOnly(globalsFile);
549
723
  try {
550
- await execa(dumpallBin, buildPgDumpallGlobalsArgs({
551
- connectionString: opts.connectionString,
552
- outFile: globalsFile
553
- }), {
554
- stdio: opts.inheritStdio ? "inherit" : "pipe",
555
- env: { ...env }
556
- });
724
+ try {
725
+ await execa(dumpallBin, buildPgDumpallGlobalsArgs({
726
+ connectionString: tool.connectionString,
727
+ outFile: globalsFile
728
+ }), {
729
+ stdio: opts.inheritStdio ? "inherit" : "pipe",
730
+ env: tool.env
731
+ });
732
+ } finally {
733
+ await resetSharedSession(opts.connectionString);
734
+ }
557
735
  } catch (error) {
558
736
  discardPartialDump(globalsFile);
559
737
  discardPartialDump(localFile);
@@ -565,6 +743,21 @@ async function createDump(opts) {
565
743
  return result;
566
744
  }
567
745
  /**
746
+ * Create `file` empty and readable by its owner only, before a tool writes it.
747
+ *
748
+ * A dump holds every row, and the `.globals.sql` sidecar every role's password
749
+ * hash. pg_dump and pg_dumpall create their `--file=` target with the
750
+ * process's umask — 0644 on most machines, readable by every user — and keep
751
+ * the mode of a file that is already there. Created 0600 first, the file is
752
+ * never readable by anyone else, not even while the dump runs; chmod-ing it
753
+ * afterwards would leave it open for as long as that takes. A file that
754
+ * already existed is narrowed too.
755
+ */
756
+ function createOwnerOnly(file) {
757
+ fs.closeSync(fs.openSync(file, "w", 384));
758
+ fs.chmodSync(file, 384);
759
+ }
760
+ /**
568
761
  * Cheap integrity check on a freshly written dump: it must be non-empty and
569
762
  * `pg_restore --list` must parse its table of contents without error. Used
570
763
  * before pruning older backups so a corrupt-but-exit-0 dump never becomes
@@ -634,16 +827,213 @@ async function restoreDump(opts) {
634
827
  const env = opts.env ?? process.env;
635
828
  const bin = resolvePgBinary("pg_restore", env);
636
829
  if (!bin) throw new BackupToolError("Could not find the 'pg_restore' binary.", "Install the PostgreSQL client tools or set PG_RESTORE_PATH.");
637
- await execa(bin, buildPgRestoreArgs({
638
- connectionString: opts.connectionString,
830
+ const tool = libpqInvocation(opts.connectionString, env);
831
+ const args = buildPgRestoreArgs({
832
+ connectionString: tool.connectionString,
639
833
  inputFile: opts.inputFile,
640
834
  clean: opts.clean,
641
835
  noOwner: opts.noOwner,
642
836
  exitOnError: opts.exitOnError
837
+ });
838
+ try {
839
+ await execa(bin, args, {
840
+ stdio: opts.inheritStdio ? "inherit" : "pipe",
841
+ env: tool.env
842
+ });
843
+ } finally {
844
+ await resetSharedSession(opts.connectionString);
845
+ }
846
+ }
847
+ /**
848
+ * A `restore --clean` that stopped, and so was rolled back whole.
849
+ *
850
+ * `tool` is the one that stopped it: `pg_restore`, writing the dump's script,
851
+ * or `psql`, running it. `lockTimeout` when what `psql` stopped on was the
852
+ * wait for a lock — `CLEAN_LOCK_TIMEOUT` in `pg-tools.ts`.
853
+ */
854
+ var CleanRestoreFailed = class extends Error {
855
+ tool;
856
+ exitCode;
857
+ lockTimeout;
858
+ constructor(tool, exitCode, lockTimeout) {
859
+ super(`${tool} exited with code ${exitCode ?? "unknown"}`);
860
+ this.tool = tool;
861
+ this.exitCode = exitCode;
862
+ this.lockTimeout = lockTimeout;
863
+ this.name = "CleanRestoreFailed";
864
+ }
865
+ };
866
+ /**
867
+ * Restore a dump over what the database holds, as one transaction:
868
+ * {@link cleanRestorePrelude} first, then the dump as `pg_restore` writes it
869
+ * for `psql`, and the `COMMIT` only after the last of it.
870
+ *
871
+ * One unit, so it either replaces what the dump holds or changes nothing:
872
+ * `pg_restore --clean` runs each statement on its own, so the drops it starts
873
+ * with had all happened when a later one failed — and its plain `DROP SCHEMA
874
+ * rebase` fails on any table the backup lacks, which left a database with
875
+ * every table dropped and nothing restored. Here a failure anywhere —
876
+ * `pg_restore` cannot read the archive, a statement fails, the connection
877
+ * drops — ends the transaction without a `COMMIT`, and Postgres rolls it back.
878
+ *
879
+ * Throws {@link CleanRestoreFailed} when it did not commit.
880
+ */
881
+ async function restoreDumpReplacing(opts) {
882
+ const env = opts.env ?? process.env;
883
+ const restoreBin = resolvePgBinary("pg_restore", env);
884
+ const psqlBin = resolvePgBinary("psql", env);
885
+ if (!restoreBin || !psqlBin) {
886
+ const tool = restoreBin ? "psql" : "pg_restore";
887
+ throw new BackupToolError(`Could not find the '${tool}' binary${tool === "psql" ? ", which runs a --clean restore as one transaction" : ""}.`, `Install the PostgreSQL client tools or set ${pgToolVariable(tool)}.`);
888
+ }
889
+ const tool = libpqInvocation(opts.connectionString, env);
890
+ const script = execa(restoreBin, buildPgRestoreScriptArgs({
891
+ inputFile: opts.inputFile,
892
+ noOwner: opts.noOwner
643
893
  }), {
644
- stdio: opts.inheritStdio ? "inherit" : "pipe",
645
- env: { ...env }
894
+ stdin: "ignore",
895
+ stdout: "pipe",
896
+ stderr: opts.inheritStdio ? "inherit" : "ignore",
897
+ buffer: false,
898
+ reject: false,
899
+ env: tool.env
900
+ });
901
+ const apply = execa(psqlBin, buildPsqlScriptArgs(tool.connectionString), {
902
+ stdin: "pipe",
903
+ stdout: "ignore",
904
+ stderr: opts.inheritStdio ? ["pipe", "inherit"] : "pipe",
905
+ reject: false,
906
+ env: tool.env
907
+ });
908
+ const sink = apply.stdin;
909
+ const source = script.stdout;
910
+ if (!sink || !source) throw new BackupToolError("Could not connect pg_restore to psql.");
911
+ sink.on("error", () => void 0);
912
+ apply.once("exit", () => {
913
+ source.unpipe(sink);
914
+ source.resume();
915
+ script.kill();
916
+ });
917
+ sink.write(["BEGIN", ...cleanRestorePrelude(opts.objects)].map((statement) => `${statement};\n`).join(""));
918
+ source.pipe(sink, { end: false });
919
+ const written = await script;
920
+ const committed = written.exitCode === 0 && sink.writable;
921
+ sink.end(committed ? "COMMIT;\n" : void 0);
922
+ const applied = await apply;
923
+ await resetSharedSession(opts.connectionString);
924
+ if (applied.exitCode !== 0) {
925
+ const stderr = typeof applied.stderr === "string" ? applied.stderr : "";
926
+ throw new CleanRestoreFailed("psql", applied.exitCode, /lock timeout/i.test(stderr));
927
+ }
928
+ if (!committed) throw new CleanRestoreFailed("pg_restore", written.exitCode, false);
929
+ }
930
+ /**
931
+ * What `restore --clean` drops that the backup does not hold, so it can say
932
+ * so before it runs: the tables and views in a schema the backup replaces
933
+ * whole that it has no copy of, and the views and foreign keys elsewhere that
934
+ * reference one of the relations it replaces. As `<kind> <name>`.
935
+ *
936
+ * None for a database that does not exist yet.
937
+ */
938
+ async function findCleanExtras(connectionString, objects) {
939
+ const schemas = objects.schemas.filter((schema) => schema !== "public");
940
+ if (schemas.length === 0 && objects.relations.length === 0) return [];
941
+ const { Client } = await import("pg");
942
+ const client = new Client({ connectionString });
943
+ try {
944
+ await client.connect();
945
+ } catch (err) {
946
+ if (err instanceof Error && "code" in err && err.code === "3D000") return [];
947
+ throw err;
948
+ }
949
+ try {
950
+ const { rows } = await client.query(`WITH dumped AS (
951
+ SELECT c.oid
952
+ FROM pg_class c
953
+ JOIN pg_namespace n ON n.oid = c.relnamespace
954
+ JOIN unnest($2::text[], $3::text[]) AS d(schema_name, rel_name)
955
+ ON d.schema_name = n.nspname AND d.rel_name = c.relname
956
+ )
957
+ SELECT kind, name FROM (
958
+ SELECT CASE c.relkind WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view'
959
+ WHEN 'f' THEN 'foreign table' ELSE 'table' END AS kind,
960
+ n.nspname || '.' || c.relname AS name
961
+ FROM pg_class c
962
+ JOIN pg_namespace n ON n.oid = c.relnamespace
963
+ WHERE n.nspname = ANY($1::text[])
964
+ AND c.relkind IN ('r', 'p', 'v', 'm', 'f')
965
+ AND c.oid NOT IN (SELECT oid FROM dumped)
966
+ UNION
967
+ SELECT 'view', vn.nspname || '.' || v.relname
968
+ FROM pg_depend dep
969
+ JOIN pg_rewrite r ON r.oid = dep.objid AND dep.classid = 'pg_rewrite'::regclass
970
+ JOIN pg_class v ON v.oid = r.ev_class
971
+ JOIN pg_namespace vn ON vn.oid = v.relnamespace
972
+ WHERE dep.refobjid IN (SELECT oid FROM dumped)
973
+ AND v.oid NOT IN (SELECT oid FROM dumped)
974
+ AND NOT vn.nspname = ANY($1::text[])
975
+ UNION
976
+ SELECT 'foreign key', con.conname || ' on ' || cn.nspname || '.' || cr.relname
977
+ FROM pg_constraint con
978
+ JOIN pg_class cr ON cr.oid = con.conrelid
979
+ JOIN pg_namespace cn ON cn.oid = cr.relnamespace
980
+ WHERE con.contype = 'f'
981
+ AND con.confrelid IN (SELECT oid FROM dumped)
982
+ AND con.conrelid NOT IN (SELECT oid FROM dumped)
983
+ AND NOT cn.nspname = ANY($1::text[])
984
+ ) extra
985
+ ORDER BY name`, [
986
+ schemas,
987
+ objects.relations.map((r) => r.schema),
988
+ objects.relations.map((r) => r.name)
989
+ ]);
990
+ return rows.map((row) => `${row.kind} ${row.name}`);
991
+ } finally {
992
+ await client.end();
993
+ }
994
+ }
995
+ /** The schemas and tables a dump creates, from `pg_restore --list`. See {@link parseDumpObjects}. */
996
+ async function readDumpObjects(inputFile, env = process.env) {
997
+ const bin = resolvePgBinary("pg_restore", env);
998
+ if (!bin) throw new BackupToolError("Could not find the 'pg_restore' binary.", "Install the PostgreSQL client tools or set PG_RESTORE_PATH.");
999
+ const toolEnv = Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== void 0));
1000
+ const { stdout } = await execa(bin, buildPgRestoreListArgs(inputFile), {
1001
+ stdio: "pipe",
1002
+ env: toolEnv
646
1003
  });
1004
+ return parseDumpObjects(stdout);
1005
+ }
1006
+ /**
1007
+ * Which of a dump's schemas and tables the target database already holds,
1008
+ * as `schema <name>` / `table <schema>.<name>`: what a restore without
1009
+ * `--clean` would stop on. `public` is left out of the schemas — every
1010
+ * database has it, and pg_restore does not create it. None for a database
1011
+ * that does not exist yet.
1012
+ */
1013
+ async function findRestoreConflicts(connectionString, objects) {
1014
+ const schemas = objects.schemas.filter((schema) => schema !== "public");
1015
+ if (schemas.length === 0 && objects.tables.length === 0) return [];
1016
+ const { Client } = await import("pg");
1017
+ const client = new Client({ connectionString });
1018
+ try {
1019
+ await client.connect();
1020
+ } catch (err) {
1021
+ if (err instanceof Error && "code" in err && err.code === "3D000") return [];
1022
+ throw err;
1023
+ }
1024
+ try {
1025
+ const existingSchemas = await client.query("SELECT nspname FROM pg_namespace WHERE nspname = ANY($1::text[]) ORDER BY nspname", [schemas]);
1026
+ const existingTables = await client.query(`SELECT n.nspname, c.relname
1027
+ FROM pg_class c
1028
+ JOIN pg_namespace n ON n.oid = c.relnamespace
1029
+ JOIN unnest($1::text[], $2::text[]) AS d(schema_name, table_name)
1030
+ ON d.schema_name = n.nspname AND d.table_name = c.relname
1031
+ WHERE c.relkind IN ('r', 'p')
1032
+ ORDER BY n.nspname, c.relname`, [objects.tables.map((t) => t.schema), objects.tables.map((t) => t.table)]);
1033
+ return [...existingSchemas.rows.map((row) => `schema ${row.nspname}`), ...existingTables.rows.map((row) => `table ${row.nspname}.${row.relname}`)];
1034
+ } finally {
1035
+ await client.end();
1036
+ }
647
1037
  }
648
1038
  /**
649
1039
  * Create a database (if absent) by connecting to the maintenance
@@ -742,6 +1132,6 @@ async function pruneBackups(dest, options, storage) {
742
1132
  return toDelete;
743
1133
  }
744
1134
  //#endregion
745
- export { parseDbNameFromUrl as A, buildRowSecurityPgOptions as C, joinStorageKey as D, globalsFileForDump as E, withDatabaseName as F, resolveConnectionString as M, serverVersionNumToMajor as N, parseBackupDestination as O, splitGlobalsStatements as P, buildPgRestoreListArgs as S, diagnoseRowSecurityDumpFailure as T, selectBackupsToPrune as _, detectToolMajor as a, buildPgDumpallGlobalsArgs as b, getServerVersionMajor as c, pruneBackups as d, restoreDump as f, resolvePgBinary as g, pgToolVariable as h, createDump as i, parsePgToolMajor as j, parseBackupTimestamp as k, listBackups as l, validateDump as m, applyGlobals as n, discardPartialDump as o, uploadBackup as p, backup_service_exports as r, ensureDatabaseExists as s, BackupToolError as t, preflight as u, buildBackupFilename as v, checkToolServerCompatibility as w, buildPgRestoreArgs as x, buildPgDumpArgs as y };
1135
+ export { buildPgRestoreScriptArgs as A, parseDbNameFromUrl as B, selectBackupsToPrune as C, buildPgDumpallGlobalsArgs as D, buildPgDumpArgs as E, diagnoseRowSecurityDumpFailure as F, splitGlobalsStatements as G, parsePgToolMajor as H, globalsFileForDump as I, withDatabaseName as K, joinStorageKey as L, buildRowSecurityPgOptions as M, checkToolServerCompatibility as N, buildPgRestoreArgs as O, cleanRestorePrelude as P, parseBackupDestination as R, resolvePgBinary as S, buildBackupFilename as T, resolveConnectionString as U, parseDumpObjects as V, serverVersionNumToMajor as W, restoreDump as _, createDump as a, validateDump as b, ensureDatabaseExists as c, getServerVersionMajor as d, listBackups as f, resetSharedSession as g, readDumpObjects as h, backup_service_exports as i, buildPsqlScriptArgs as j, buildPgRestoreListArgs as k, findCleanExtras as l, pruneBackups as m, CleanRestoreFailed as n, detectToolMajor as o, preflight as p, applyGlobals as r, discardPartialDump as s, BackupToolError as t, findRestoreConflicts as u, restoreDumpReplacing as v, CLEAN_LOCK_TIMEOUT as w, pgToolVariable as x, uploadBackup as y, parseBackupTimestamp as z };
746
1136
 
747
- //# sourceMappingURL=backup-service-BTBB7RXn.js.map
1137
+ //# sourceMappingURL=backup-service-DytMimsL.js.map