@rebasepro/server-postgres 0.15.0 → 0.16.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 (45) hide show
  1. package/dist/PostgresBackendDriver.d.ts +1 -1
  2. package/dist/PostgresBootstrapper.d.ts +0 -11
  3. package/dist/auth/services.d.ts +2 -2
  4. package/dist/{auth-users-columns-JJ8ngvy5.js → auth-users-columns-CgyPWQ18.js} +3 -3
  5. package/dist/auth-users-columns-CgyPWQ18.js.map +1 -0
  6. package/dist/backup/backup-logic.d.ts +20 -0
  7. package/dist/backup/backup-service.d.ts +6 -0
  8. package/dist/backup/retention.d.ts +11 -0
  9. package/dist/{backup-service-czK-OAuG.js → backup-service-BZoixhVl.js} +56 -11
  10. package/dist/backup-service-BZoixhVl.js.map +1 -0
  11. package/dist/collections-schema-version-BMeu3cgv.js +81 -0
  12. package/dist/collections-schema-version-BMeu3cgv.js.map +1 -0
  13. package/dist/{ensure-collection-policies-D5PtQLyR.js → ensure-collection-policies-BVFb2olB.js} +4 -3
  14. package/dist/ensure-collection-policies-BVFb2olB.js.map +1 -0
  15. package/dist/{ensure-collection-tables-BHUjQ-z4.js → ensure-collection-tables-BY1pHRD_.js} +2 -2
  16. package/dist/{ensure-collection-tables-BHUjQ-z4.js.map → ensure-collection-tables-BY1pHRD_.js.map} +1 -1
  17. package/dist/index.es.js +65 -21
  18. package/dist/index.es.js.map +1 -1
  19. package/dist/{rls-enforcement-BDBfuTD4.js → rls-enforcement-Ch0T6OwW.js} +7 -7
  20. package/dist/rls-enforcement-Ch0T6OwW.js.map +1 -0
  21. package/dist/schema/collections-schema-version.d.ts +32 -0
  22. package/dist/security/policy-drift.d.ts +2 -2
  23. package/dist/security/rls-enforcement.d.ts +6 -6
  24. package/dist/services/realtimeService.d.ts +3 -1
  25. package/dist/src-BBFsDaeA.js.map +1 -1
  26. package/dist/websocket-BVgDVO-V.js.map +1 -1
  27. package/package.json +6 -6
  28. package/src/PostgresAdapter.ts +14 -0
  29. package/src/PostgresBackendDriver.ts +3 -3
  30. package/src/PostgresBootstrapper.ts +82 -12
  31. package/src/auth/services.ts +2 -2
  32. package/src/backup/backup-cli.ts +49 -12
  33. package/src/backup/backup-logic.ts +31 -0
  34. package/src/backup/backup-service.ts +42 -8
  35. package/src/backup/pg-tools.ts +12 -1
  36. package/src/backup/retention.ts +11 -0
  37. package/src/schema/collections-schema-version.ts +103 -0
  38. package/src/security/policy-drift.test.ts +25 -6
  39. package/src/security/policy-drift.ts +14 -6
  40. package/src/security/rls-enforcement.ts +7 -7
  41. package/src/services/realtimeService.ts +9 -1
  42. package/dist/auth-users-columns-JJ8ngvy5.js.map +0 -1
  43. package/dist/backup-service-czK-OAuG.js.map +0 -1
  44. package/dist/ensure-collection-policies-D5PtQLyR.js.map +0 -1
  45. package/dist/rls-enforcement-BDBfuTD4.js.map +0 -1
@@ -21,3 +21,23 @@ export declare function applyGlobalsWith(runStatement: (sql: string) => Promise<
21
21
  * `deleteObject` removes one key and rejects if it cannot (e.g. not found).
22
22
  */
23
23
  export declare function pruneWith(keys: string[], deleteObject: (key: string) => Promise<void>): Promise<void>;
24
+ /**
25
+ * Remove a dump artifact abandoned by a failed tool run.
26
+ *
27
+ * `pg_dump` creates its `--file=` target before it finishes connecting, so any
28
+ * failure — a URL libpq rejects, a dropped connection, a full disk — leaves a
29
+ * 0-byte file behind. That corpse is not inert: `rebase db backups list` used
30
+ * to show it as an ordinary entry, and `selectBackupsToPrune` ranks by
31
+ * timestamp alone, so it occupies a protected `keepMinimum` slot and can push
32
+ * a real backup out of retention.
33
+ *
34
+ * Best-effort on purpose: the caller is already throwing, and a failed unlink
35
+ * must not replace the real diagnosis with an ENOENT/EPERM from the cleanup.
36
+ *
37
+ * Lives here rather than in `backup-service.ts` for the reason at the top of
38
+ * this file — that module's top-level `execa` import makes it unloadable under
39
+ * jest, so anything placed there cannot be unit-tested.
40
+ *
41
+ * `remove` deletes one file and throws if it cannot; `exists` reports presence.
42
+ */
43
+ export declare function discardPartialDumpWith(exists: (file: string) => boolean, remove: (file: string) => void, file: string): void;
@@ -1,6 +1,12 @@
1
1
  import type { StorageController } from "@rebasepro/server";
2
2
  import { BackupDestination, type RowSecurityIdentity, VersionCompatibility } from "./pg-tools";
3
3
  import { BackupObject, RetentionOptions } from "./retention";
4
+ /**
5
+ * Remove a dump artifact abandoned by a failed tool run. See
6
+ * {@link discardPartialDumpWith} for why this exists and why the logic lives
7
+ * in the execa-free module.
8
+ */
9
+ export declare function discardPartialDump(file: string): void;
4
10
  export declare class BackupToolError extends Error {
5
11
  readonly hint?: string | undefined;
6
12
  constructor(message: string, hint?: string | undefined);
@@ -6,6 +6,17 @@ export interface BackupObject {
6
6
  * timestamp encoded in the key by `buildBackupFilename`.
7
7
  */
8
8
  createdAt?: Date;
9
+ /**
10
+ * Size on disk, when the lister knows it (local destinations always do).
11
+ *
12
+ * Reported so an empty artifact cannot masquerade as a backup in
13
+ * `rebase db backups list`. Deliberately NOT consulted by
14
+ * {@link selectBackupsToPrune}: retention decides by age, and a rule that
15
+ * silently skipped small files would make a genuinely tiny dump immortal.
16
+ * Visibility is the fix here; the artifacts themselves are now removed at
17
+ * the point of failure by `discardPartialDump`.
18
+ */
19
+ sizeBytes?: number;
9
20
  }
10
21
  export interface RetentionOptions {
11
22
  /** Delete backups older than this many days. `0`/negative disables age pruning. */
@@ -344,7 +344,10 @@ function globalsFileForDump(dumpPath) {
344
344
  * abort the rest. Drops `--` comment lines and blank statements.
345
345
  */
346
346
  function splitGlobalsStatements(sql) {
347
- return sql.split("\n").filter((line) => !line.trim().startsWith("--")).join("\n").split(";").map((s) => s.trim()).filter((s) => s.length > 0);
347
+ return sql.split("\n").filter((line) => {
348
+ const trimmed = line.trim();
349
+ return !trimmed.startsWith("--") && !trimmed.startsWith("\\");
350
+ }).join("\n").split(";").map((s) => s.trim()).filter((s) => s.length > 0);
348
351
  }
349
352
  /**
350
353
  * Resolve the Postgres connection string the backup commands should use,
@@ -8696,6 +8699,30 @@ async function pruneWith(keys, deleteObject) {
8696
8699
  } catch {}
8697
8700
  }
8698
8701
  }
8702
+ /**
8703
+ * Remove a dump artifact abandoned by a failed tool run.
8704
+ *
8705
+ * `pg_dump` creates its `--file=` target before it finishes connecting, so any
8706
+ * failure — a URL libpq rejects, a dropped connection, a full disk — leaves a
8707
+ * 0-byte file behind. That corpse is not inert: `rebase db backups list` used
8708
+ * to show it as an ordinary entry, and `selectBackupsToPrune` ranks by
8709
+ * timestamp alone, so it occupies a protected `keepMinimum` slot and can push
8710
+ * a real backup out of retention.
8711
+ *
8712
+ * Best-effort on purpose: the caller is already throwing, and a failed unlink
8713
+ * must not replace the real diagnosis with an ENOENT/EPERM from the cleanup.
8714
+ *
8715
+ * Lives here rather than in `backup-service.ts` for the reason at the top of
8716
+ * this file — that module's top-level `execa` import makes it unloadable under
8717
+ * jest, so anything placed there cannot be unit-tested.
8718
+ *
8719
+ * `remove` deletes one file and throws if it cannot; `exists` reports presence.
8720
+ */
8721
+ function discardPartialDumpWith(exists, remove, file) {
8722
+ try {
8723
+ if (exists(file)) remove(file);
8724
+ } catch {}
8725
+ }
8699
8726
  //#endregion
8700
8727
  //#region src/backup/backup-service.ts
8701
8728
  /**
@@ -8712,6 +8739,7 @@ var backup_service_exports = /* @__PURE__ */ __exportAll({
8712
8739
  applyGlobals: () => applyGlobals,
8713
8740
  createDump: () => createDump,
8714
8741
  detectToolMajor: () => detectToolMajor,
8742
+ discardPartialDump: () => discardPartialDump,
8715
8743
  ensureDatabaseExists: () => ensureDatabaseExists,
8716
8744
  getServerVersionMajor: () => getServerVersionMajor,
8717
8745
  listBackups: () => listBackups,
@@ -8722,6 +8750,14 @@ var backup_service_exports = /* @__PURE__ */ __exportAll({
8722
8750
  uploadBackup: () => uploadBackup,
8723
8751
  validateDump: () => validateDump
8724
8752
  });
8753
+ /**
8754
+ * Remove a dump artifact abandoned by a failed tool run. See
8755
+ * {@link discardPartialDumpWith} for why this exists and why the logic lives
8756
+ * in the execa-free module.
8757
+ */
8758
+ function discardPartialDump(file) {
8759
+ discardPartialDumpWith((f) => fs.existsSync(f), (f) => fs.unlinkSync(f), file);
8760
+ }
8725
8761
  var BackupToolError = class extends Error {
8726
8762
  hint;
8727
8763
  constructor(message, hint) {
@@ -8806,6 +8842,7 @@ async function createDump(opts) {
8806
8842
  env: dumpEnv
8807
8843
  });
8808
8844
  } catch (error) {
8845
+ discardPartialDump(localFile);
8809
8846
  const diagnosis = diagnoseRowSecurityDumpFailure(error);
8810
8847
  if (!diagnosis) throw error;
8811
8848
  throw new BackupToolError(diagnosis, "Run `rebase db backup --help` for the flag, and read what it says about partial dumps.");
@@ -8819,13 +8856,19 @@ async function createDump(opts) {
8819
8856
  const dumpallBin = resolvePgBinary("pg_dumpall", env);
8820
8857
  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.");
8821
8858
  const globalsFile = globalsFileForDump(localFile);
8822
- await execa(dumpallBin, buildPgDumpallGlobalsArgs({
8823
- connectionString: opts.connectionString,
8824
- outFile: globalsFile
8825
- }), {
8826
- stdio: opts.inheritStdio ? "inherit" : "pipe",
8827
- env: { ...env }
8828
- });
8859
+ try {
8860
+ await execa(dumpallBin, buildPgDumpallGlobalsArgs({
8861
+ connectionString: opts.connectionString,
8862
+ outFile: globalsFile
8863
+ }), {
8864
+ stdio: opts.inheritStdio ? "inherit" : "pipe",
8865
+ env: { ...env }
8866
+ });
8867
+ } catch (error) {
8868
+ discardPartialDump(globalsFile);
8869
+ discardPartialDump(localFile);
8870
+ throw error;
8871
+ }
8829
8872
  result.globalsFile = globalsFile;
8830
8873
  result.globalsSizeBytes = fs.existsSync(globalsFile) ? fs.statSync(globalsFile).size : 0;
8831
8874
  }
@@ -8962,9 +9005,11 @@ async function listBackups(dest, storage) {
8962
9005
  const dir = fs.statSync(dest.path).isDirectory() ? dest.path : path.dirname(dest.path);
8963
9006
  return fs.readdirSync(dir).filter((f) => f.endsWith(".dump")).map((f) => {
8964
9007
  const full = path.join(dir, f);
9008
+ const stats = fs.statSync(full);
8965
9009
  return {
8966
9010
  key: full,
8967
- createdAt: parseBackupTimestamp(f) ?? fs.statSync(full).mtime
9011
+ createdAt: parseBackupTimestamp(f) ?? stats.mtime,
9012
+ sizeBytes: stats.size
8968
9013
  };
8969
9014
  }).sort((a, b) => (b.createdAt?.getTime() ?? 0) - (a.createdAt?.getTime() ?? 0));
8970
9015
  }
@@ -8993,6 +9038,6 @@ async function pruneBackups(dest, options, storage) {
8993
9038
  return toDelete;
8994
9039
  }
8995
9040
  //#endregion
8996
- export { parsePgToolMajor as A, checkToolServerCompatibility as C, parseBackupDestination as D, joinStorageKey as E, serverVersionNumToMajor as M, splitGlobalsStatements as N, parseBackupTimestamp as O, withDatabaseName as P, buildRowSecurityPgOptions as S, globalsFileForDump as T, buildBackupFilename as _, detectToolMajor as a, buildPgRestoreArgs as b, listBackups as c, resolvePgBinary as d, restoreDump as f, selectBackupsToPrune as g, require_source as h, createDump as i, resolveConnectionString as j, parseDbNameFromUrl as k, preflight as l, validateDump as m, applyGlobals as n, ensureDatabaseExists as o, uploadBackup as p, backup_service_exports as r, getServerVersionMajor as s, BackupToolError as t, pruneBackups as u, buildPgDumpArgs as v, diagnoseRowSecurityDumpFailure as w, buildPgRestoreListArgs as x, buildPgDumpallGlobalsArgs as y };
9041
+ 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, resolvePgBinary as f, require_source as g, validateDump as h, createDump as i, parsePgToolMajor as j, parseBackupTimestamp as k, listBackups as l, uploadBackup as m, applyGlobals as n, discardPartialDump as o, restoreDump 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 };
8997
9042
 
8998
- //# sourceMappingURL=backup-service-czK-OAuG.js.map
9043
+ //# sourceMappingURL=backup-service-BZoixhVl.js.map