@rebasepro/server-postgres 0.12.1-canary.g4e7bcbf → 0.12.1-canary.g52d71ee
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/auth/services.d.ts +6 -1
- package/dist/backup/backup-service.d.ts +10 -1
- package/dist/backup/pg-tools.d.ts +47 -0
- package/dist/{backup-service-DLb2drIH.js → backup-service-CD8o_1Sl.js} +136 -10
- package/dist/backup-service-CD8o_1Sl.js.map +1 -0
- package/dist/{ensure-collection-policies-Dv21KDMJ.js → ensure-collection-policies-ViG8XiPn.js} +2 -2
- package/dist/{ensure-collection-policies-Dv21KDMJ.js.map → ensure-collection-policies-ViG8XiPn.js.map} +1 -1
- package/dist/{ensure-collection-tables-CT6xHB1d.js → ensure-collection-tables-CBQdOETu.js} +2 -2
- package/dist/{ensure-collection-tables-CT6xHB1d.js.map → ensure-collection-tables-CBQdOETu.js.map} +1 -1
- package/dist/index.es.js +768 -537
- package/dist/index.es.js.map +1 -1
- package/dist/schema/introspect-db-constraints.d.ts +57 -0
- package/dist/schema/introspect-db-logic.d.ts +94 -5
- package/dist/schema/introspect-db-queries.d.ts +119 -0
- package/dist/schema/introspect-db-structure.d.ts +263 -0
- package/dist/schema/introspect-db-types.d.ts +11 -0
- package/dist/services/RelationService.d.ts +24 -1
- package/dist/services/channel-bus/index.d.ts +1 -7
- package/dist/services/collection-helpers.d.ts +36 -2
- package/dist/{src-BkdpiQdw.js → src-DlPBctw_.js} +72 -6
- package/dist/src-DlPBctw_.js.map +1 -0
- package/dist/utils/connection-string.d.ts +29 -0
- package/dist/utils/drizzle-conditions.d.ts +5 -4
- package/dist/utils/pg-error-utils.d.ts +16 -0
- package/package.json +6 -6
- package/src/auth/services.ts +6 -3
- package/src/backup/backup-cli.ts +41 -2
- package/src/backup/backup-service.ts +38 -5
- package/src/backup/pg-tools.ts +96 -3
- package/src/cli.ts +11 -4
- package/src/collections/validate-relations.ts +15 -0
- package/src/data-transformer.ts +9 -3
- package/src/schema/generate-drizzle-schema-logic.ts +26 -1
- package/src/schema/introspect-db-constraints.ts +385 -0
- package/src/schema/introspect-db-inference.ts +18 -8
- package/src/schema/introspect-db-logic.ts +364 -68
- package/src/schema/introspect-db-queries.ts +326 -0
- package/src/schema/introspect-db-structure.ts +670 -0
- package/src/schema/introspect-db-types.ts +56 -0
- package/src/schema/introspect-db.ts +37 -80
- package/src/services/BranchService.ts +66 -28
- package/src/services/FetchService.ts +14 -0
- package/src/services/PersistService.ts +20 -6
- package/src/services/RelationService.ts +211 -45
- package/src/services/channel-bus/index.ts +0 -9
- package/src/services/collection-helpers.ts +69 -3
- package/src/utils/connection-string.ts +58 -0
- package/src/utils/drizzle-conditions.ts +31 -6
- package/src/utils/pg-error-utils.ts +19 -0
- package/dist/backup-service-DLb2drIH.js.map +0 -1
- package/dist/src-BkdpiQdw.js.map +0 -1
package/dist/auth/services.d.ts
CHANGED
|
@@ -23,8 +23,13 @@ export interface AuthSchemaTables {
|
|
|
23
23
|
*
|
|
24
24
|
* Whitespace goes too: a trailing space survives the fold and reproduces the
|
|
25
25
|
* problem exactly.
|
|
26
|
+
*
|
|
27
|
+
* Re-exported rather than defined here: `@rebasepro/server` and
|
|
28
|
+
* `@rebasepro/server-mongo` write this column too, and a second copy of this
|
|
29
|
+
* rule is the defect it exists to prevent.
|
|
26
30
|
*/
|
|
27
|
-
|
|
31
|
+
import { normalizeEmail } from "@rebasepro/common";
|
|
32
|
+
export { normalizeEmail };
|
|
28
33
|
/**
|
|
29
34
|
* PostgreSQL implementation of UserRepository.
|
|
30
35
|
* Handles all user-related database operations using Drizzle ORM.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { StorageController } from "@rebasepro/server";
|
|
2
|
-
import { BackupDestination, VersionCompatibility } from "./pg-tools";
|
|
2
|
+
import { BackupDestination, type RowSecurityIdentity, VersionCompatibility } from "./pg-tools";
|
|
3
3
|
import { BackupObject, RetentionOptions } from "./retention";
|
|
4
4
|
export declare class BackupToolError extends Error {
|
|
5
5
|
readonly hint?: string | undefined;
|
|
@@ -54,6 +54,15 @@ export declare function createDump(opts: {
|
|
|
54
54
|
inheritStdio?: boolean;
|
|
55
55
|
includeGlobals?: boolean;
|
|
56
56
|
env?: Record<string, string | undefined>;
|
|
57
|
+
/**
|
|
58
|
+
* Dump with row security left on, reading as this identity.
|
|
59
|
+
*
|
|
60
|
+
* The escape hatch for a managed Postgres, where the dumping role owns
|
|
61
|
+
* nothing and has no `BYPASSRLS`. Off by default, and deliberately so: with
|
|
62
|
+
* row security on, `pg_dump` stops erroring on rows it cannot see and
|
|
63
|
+
* simply omits them. See {@link RowSecurityIdentity}.
|
|
64
|
+
*/
|
|
65
|
+
rowSecurity?: RowSecurityIdentity;
|
|
57
66
|
}): Promise<BackupResult>;
|
|
58
67
|
/**
|
|
59
68
|
* Cheap integrity check on a freshly written dump: it must be non-empty and
|
|
@@ -79,6 +79,36 @@ export declare function parseBackupDestination(out: string): BackupDestination;
|
|
|
79
79
|
* doubled slash.
|
|
80
80
|
*/
|
|
81
81
|
export declare function joinStorageKey(prefix: string, fileName: string): string;
|
|
82
|
+
/**
|
|
83
|
+
* The identity `pg_dump` reads rows as, when row security is left on.
|
|
84
|
+
*
|
|
85
|
+
* Not optional, and that is the whole design. `pg_dump --enable-row-security`
|
|
86
|
+
* on its own is the dangerous command in this file: it turns the "query would
|
|
87
|
+
* be affected by row-level security policy" *error* into a dump that exits 0
|
|
88
|
+
* and is silently missing every row the dumping role's policies exclude. A
|
|
89
|
+
* backup that looks fine and restores most of your data is worse than one that
|
|
90
|
+
* refused to run.
|
|
91
|
+
*
|
|
92
|
+
* So the flag is unreachable without a subject to evaluate the policies
|
|
93
|
+
* against. Rebase's generated policies read `app.uid` and `app.user_roles`;
|
|
94
|
+
* supplying an admin role satisfies the `admin_full_access` rule and the dump
|
|
95
|
+
* sees everything that rule sees.
|
|
96
|
+
*/
|
|
97
|
+
export interface RowSecurityIdentity {
|
|
98
|
+
/** Written to `app.uid`. Any non-empty value — it is only an audit trail. */
|
|
99
|
+
uid: string;
|
|
100
|
+
/** Written to `app.user_roles`. Must include a role the policies admit. */
|
|
101
|
+
roles: string[];
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* `PGOPTIONS` carrying an identity, for a libpq tool that has no other way to
|
|
105
|
+
* set a GUC.
|
|
106
|
+
*
|
|
107
|
+
* A backslash escape rather than quoting, which is what libpq's `-c` parsing
|
|
108
|
+
* takes: a space inside a value ends the option otherwise, so a role list is
|
|
109
|
+
* comma-joined and never spaced.
|
|
110
|
+
*/
|
|
111
|
+
export declare function buildRowSecurityPgOptions(identity: RowSecurityIdentity): string;
|
|
82
112
|
/**
|
|
83
113
|
* Assemble the `pg_dump` argument vector. Uses the custom format (`-Fc`),
|
|
84
114
|
* which is compressed and restorable selectively via `pg_restore`.
|
|
@@ -90,7 +120,24 @@ export declare function buildPgDumpArgs(opts: {
|
|
|
90
120
|
excludeSchemas?: string[];
|
|
91
121
|
/** Number of parallel jobs (directory format only; ignored for -Fc). */
|
|
92
122
|
noOwner?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Dump with row security on, as this identity. Omit — which is the default
|
|
125
|
+
* — and `pg_dump` errors rather than skipping rows it cannot see.
|
|
126
|
+
*/
|
|
127
|
+
rowSecurity?: RowSecurityIdentity;
|
|
93
128
|
}): string[];
|
|
129
|
+
/**
|
|
130
|
+
* Whether a `pg_dump` failure is the row-security one, and what to do about it.
|
|
131
|
+
*
|
|
132
|
+
* The error text names the table and nothing else, so the first read of it is
|
|
133
|
+
* "why would a backup be affected by RLS at all?" — the answer being that the
|
|
134
|
+
* dumping role is not the tables' owner and has no `BYPASSRLS`, which is the
|
|
135
|
+
* normal state of the `postgres` user on Cloud SQL, RDS and every other managed
|
|
136
|
+
* Postgres. Nothing about that is visible from the message.
|
|
137
|
+
*
|
|
138
|
+
* Returns `null` for any other failure, so the caller reports it unchanged.
|
|
139
|
+
*/
|
|
140
|
+
export declare function diagnoseRowSecurityDumpFailure(error: unknown): string | null;
|
|
94
141
|
/**
|
|
95
142
|
* Assemble the `pg_restore` argument vector for a custom-format dump.
|
|
96
143
|
*/
|
|
@@ -25,8 +25,70 @@ import { Duplex, PassThrough, Readable, Transform, Writable, getDefaultHighWater
|
|
|
25
25
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
26
26
|
import "module";
|
|
27
27
|
import { fileURLToPath as fileURLToPath$1 } from "url";
|
|
28
|
+
//#region src/utils/connection-string.ts
|
|
29
|
+
/**
|
|
30
|
+
* Connection-string rewrites for the tools Rebase shells out to.
|
|
31
|
+
*
|
|
32
|
+
* Pure and dependency-free: `pg-tools` builds argument vectors without a live
|
|
33
|
+
* server, and these have to be usable from there.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* The `sslmode` values libpq accepts. `no-verify` is conspicuously not one.
|
|
37
|
+
*/
|
|
38
|
+
var LIBPQ_SSLMODES = /* @__PURE__ */ new Set([
|
|
39
|
+
"disable",
|
|
40
|
+
"allow",
|
|
41
|
+
"prefer",
|
|
42
|
+
"require",
|
|
43
|
+
"verify-ca",
|
|
44
|
+
"verify-full"
|
|
45
|
+
]);
|
|
46
|
+
/**
|
|
47
|
+
* Make a connection string safe to hand to a libpq program.
|
|
48
|
+
*
|
|
49
|
+
* `sslmode=no-verify` is a node-postgres convention — encrypt, but do not check
|
|
50
|
+
* the certificate — and libpq does not have it. It does not degrade or warn:
|
|
51
|
+
*
|
|
52
|
+
* psql: error: invalid sslmode value: "no-verify"
|
|
53
|
+
*
|
|
54
|
+
* which means a `DATABASE_URL` that works perfectly for the app makes `psql`,
|
|
55
|
+
* `pg_dump`, `pg_restore` and Atlas all refuse to start, with an error that
|
|
56
|
+
* points at the value rather than at the convention it comes from. It has cost
|
|
57
|
+
* this project time more than once.
|
|
58
|
+
*
|
|
59
|
+
* `require` is the honest translation: libpq's `require` encrypts and does not
|
|
60
|
+
* verify the certificate either, which is exactly what `no-verify` asks for.
|
|
61
|
+
* Nothing is relaxed by the rewrite — `verify-ca` and `verify-full` are left
|
|
62
|
+
* alone, so a connection string that asked for verification still gets it.
|
|
63
|
+
*
|
|
64
|
+
* Only `sslmode` is touched, and only when it is a value libpq would reject.
|
|
65
|
+
* Anything unparseable is returned as given: a connection that works
|
|
66
|
+
* unverified beats one this corrupted.
|
|
67
|
+
*/
|
|
68
|
+
function forLibpq(connectionString) {
|
|
69
|
+
let url;
|
|
70
|
+
try {
|
|
71
|
+
url = new URL(connectionString);
|
|
72
|
+
} catch {
|
|
73
|
+
return connectionString;
|
|
74
|
+
}
|
|
75
|
+
if (url.protocol !== "postgres:" && url.protocol !== "postgresql:") return connectionString;
|
|
76
|
+
const sslmode = url.searchParams.get("sslmode");
|
|
77
|
+
if (sslmode === null || LIBPQ_SSLMODES.has(sslmode)) return connectionString;
|
|
78
|
+
url.searchParams.set("sslmode", "require");
|
|
79
|
+
url.search = Array.from(url.searchParams.entries()).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
|
|
80
|
+
return url.toString();
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
28
83
|
//#region src/backup/pg-tools.ts
|
|
29
84
|
/**
|
|
85
|
+
* Pure helpers for the backup/restore commands.
|
|
86
|
+
*
|
|
87
|
+
* Everything in this file is side-effect free so it can be unit-tested
|
|
88
|
+
* without a live Postgres server, matching the constraint that CI must
|
|
89
|
+
* not require a database.
|
|
90
|
+
*/
|
|
91
|
+
/**
|
|
30
92
|
* Extract the database name from a Postgres connection string.
|
|
31
93
|
* Returns `null` when the URL has no database path (e.g. bare host).
|
|
32
94
|
*/
|
|
@@ -156,6 +218,22 @@ function joinStorageKey(prefix, fileName) {
|
|
|
156
218
|
return clean.length > 0 ? `${clean}/${fileName}` : fileName;
|
|
157
219
|
}
|
|
158
220
|
/**
|
|
221
|
+
* `PGOPTIONS` carrying an identity, for a libpq tool that has no other way to
|
|
222
|
+
* set a GUC.
|
|
223
|
+
*
|
|
224
|
+
* A backslash escape rather than quoting, which is what libpq's `-c` parsing
|
|
225
|
+
* takes: a space inside a value ends the option otherwise, so a role list is
|
|
226
|
+
* comma-joined and never spaced.
|
|
227
|
+
*/
|
|
228
|
+
function buildRowSecurityPgOptions(identity) {
|
|
229
|
+
const escape = (value) => value.replace(/([\\ ])/g, "\\$1");
|
|
230
|
+
return [
|
|
231
|
+
`-c app.uid=${escape(identity.uid)}`,
|
|
232
|
+
`-c app.user_id=${escape(identity.uid)}`,
|
|
233
|
+
`-c app.user_roles=${escape(identity.roles.join(","))}`
|
|
234
|
+
].join(" ");
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
159
237
|
* Assemble the `pg_dump` argument vector. Uses the custom format (`-Fc`),
|
|
160
238
|
* which is compressed and restorable selectively via `pg_restore`.
|
|
161
239
|
*/
|
|
@@ -166,18 +244,56 @@ function buildPgDumpArgs(opts) {
|
|
|
166
244
|
`--file=${opts.outFile}`
|
|
167
245
|
];
|
|
168
246
|
if (opts.noOwner) args.push("--no-owner");
|
|
247
|
+
if (opts.rowSecurity) args.push("--enable-row-security");
|
|
169
248
|
for (const schema of opts.excludeSchemas ?? []) args.push(`--exclude-schema=${schema}`);
|
|
170
|
-
args.push(opts.connectionString);
|
|
249
|
+
args.push(forLibpq(opts.connectionString));
|
|
171
250
|
return args;
|
|
172
251
|
}
|
|
173
252
|
/**
|
|
253
|
+
* Whether a `pg_dump` failure is the row-security one, and what to do about it.
|
|
254
|
+
*
|
|
255
|
+
* The error text names the table and nothing else, so the first read of it is
|
|
256
|
+
* "why would a backup be affected by RLS at all?" — the answer being that the
|
|
257
|
+
* dumping role is not the tables' owner and has no `BYPASSRLS`, which is the
|
|
258
|
+
* normal state of the `postgres` user on Cloud SQL, RDS and every other managed
|
|
259
|
+
* Postgres. Nothing about that is visible from the message.
|
|
260
|
+
*
|
|
261
|
+
* Returns `null` for any other failure, so the caller reports it unchanged.
|
|
262
|
+
*/
|
|
263
|
+
function diagnoseRowSecurityDumpFailure(error) {
|
|
264
|
+
const text = [error?.stderr, error?.message].map((part) => typeof part === "string" ? part : "").join("\n");
|
|
265
|
+
if (!/row-level security policy/i.test(text)) return null;
|
|
266
|
+
const table = text.match(/for table "([^"]+)"/)?.[1];
|
|
267
|
+
return [
|
|
268
|
+
`pg_dump cannot read ${table ? `"${table}"` : "one of the tables"} because row-level security applies to it.`,
|
|
269
|
+
"",
|
|
270
|
+
" The dumping role is neither the table's owner nor `BYPASSRLS`, which is the normal",
|
|
271
|
+
" state of the `postgres` user on Cloud SQL, RDS and other managed Postgres — there is",
|
|
272
|
+
" no superuser to hand out.",
|
|
273
|
+
"",
|
|
274
|
+
" Two ways out:",
|
|
275
|
+
"",
|
|
276
|
+
" • Grant the dumping role BYPASSRLS, or make it the owner, and run this again. The",
|
|
277
|
+
" dump then contains every row, which is what a backup should mean.",
|
|
278
|
+
"",
|
|
279
|
+
" • Re-run with --enable-row-security to dump as an admin subject instead. Rebase",
|
|
280
|
+
" sets `app.uid`/`app.user_roles` so the generated `admin_full_access` policy",
|
|
281
|
+
" admits the dump. Read the warning it prints: the result contains exactly the",
|
|
282
|
+
" rows those policies admit, and any table whose policies do not include an",
|
|
283
|
+
" admin rule comes out short — with no error.",
|
|
284
|
+
"",
|
|
285
|
+
" Do not reach for a bare `pg_dump --enable-row-security` by hand. Without the",
|
|
286
|
+
" settings above it succeeds and silently omits rows."
|
|
287
|
+
].join("\n");
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
174
290
|
* Assemble the `pg_restore` argument vector for a custom-format dump.
|
|
175
291
|
*/
|
|
176
292
|
function buildPgRestoreArgs(opts) {
|
|
177
293
|
const args = [
|
|
178
294
|
"--format=custom",
|
|
179
295
|
"--no-password",
|
|
180
|
-
`--dbname=${opts.connectionString}`
|
|
296
|
+
`--dbname=${forLibpq(opts.connectionString)}`
|
|
181
297
|
];
|
|
182
298
|
if (opts.clean) args.push("--clean", "--if-exists");
|
|
183
299
|
if (opts.noOwner) args.push("--no-owner");
|
|
@@ -210,7 +326,7 @@ function buildPgDumpallGlobalsArgs(opts) {
|
|
|
210
326
|
"--no-role-passwords",
|
|
211
327
|
"--no-password",
|
|
212
328
|
`--file=${opts.outFile}`,
|
|
213
|
-
`--dbname=${opts.connectionString}`
|
|
329
|
+
`--dbname=${forLibpq(opts.connectionString)}`
|
|
214
330
|
];
|
|
215
331
|
}
|
|
216
332
|
/**
|
|
@@ -8676,15 +8792,25 @@ async function createDump(opts) {
|
|
|
8676
8792
|
const outDir = opts.outDir ?? fs.mkdtempSync(path.join(os.tmpdir(), "rebase-backup-"));
|
|
8677
8793
|
fs.mkdirSync(outDir, { recursive: true });
|
|
8678
8794
|
const localFile = path.join(outDir, fileName);
|
|
8679
|
-
|
|
8795
|
+
const args = buildPgDumpArgs({
|
|
8680
8796
|
connectionString: opts.connectionString,
|
|
8681
8797
|
outFile: localFile,
|
|
8682
8798
|
excludeSchemas: opts.excludeSchemas,
|
|
8683
|
-
noOwner: opts.noOwner
|
|
8684
|
-
|
|
8685
|
-
stdio: opts.inheritStdio ? "inherit" : "pipe",
|
|
8686
|
-
env: { ...env }
|
|
8799
|
+
noOwner: opts.noOwner,
|
|
8800
|
+
rowSecurity: opts.rowSecurity
|
|
8687
8801
|
});
|
|
8802
|
+
const dumpEnv = { ...env };
|
|
8803
|
+
if (opts.rowSecurity) dumpEnv.PGOPTIONS = [env.PGOPTIONS, buildRowSecurityPgOptions(opts.rowSecurity)].filter(Boolean).join(" ");
|
|
8804
|
+
try {
|
|
8805
|
+
await execa(bin, args, {
|
|
8806
|
+
stdio: opts.inheritStdio ? "inherit" : "pipe",
|
|
8807
|
+
env: dumpEnv
|
|
8808
|
+
});
|
|
8809
|
+
} catch (error) {
|
|
8810
|
+
const diagnosis = diagnoseRowSecurityDumpFailure(error);
|
|
8811
|
+
if (!diagnosis) throw error;
|
|
8812
|
+
throw new BackupToolError(diagnosis, "Run `rebase db backup --help` for the flag, and read what it says about partial dumps.");
|
|
8813
|
+
}
|
|
8688
8814
|
const result = {
|
|
8689
8815
|
localFile,
|
|
8690
8816
|
fileName,
|
|
@@ -8868,6 +8994,6 @@ async function pruneBackups(dest, options, storage) {
|
|
|
8868
8994
|
return toDelete;
|
|
8869
8995
|
}
|
|
8870
8996
|
//#endregion
|
|
8871
|
-
export {
|
|
8997
|
+
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 };
|
|
8872
8998
|
|
|
8873
|
-
//# sourceMappingURL=backup-service-
|
|
8999
|
+
//# sourceMappingURL=backup-service-CD8o_1Sl.js.map
|