@rebasepro/server-postgres 0.25.1 → 0.26.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.
- package/dist/{BranchService-ClklxyAQ.js → BranchService-CWBnDhcv.js} +25 -9
- package/dist/BranchService-CWBnDhcv.js.map +1 -0
- package/dist/PostgresBackendDriver.d.ts +13 -0
- package/dist/PostgresBootstrapper.d.ts +7 -0
- package/dist/auth/services.d.ts +8 -1
- package/dist/auth/verify-auth-tables.d.ts +21 -0
- package/dist/{backup-cli-gijeXe0u.js → backup-cli-PoRG-zEq.js} +3 -3
- package/dist/{backup-cli-gijeXe0u.js.map → backup-cli-PoRG-zEq.js.map} +1 -1
- package/dist/{boot-ddl-DYH95Qeg.js → boot-ddl-EsgqDkx0.js} +2 -2
- package/dist/{boot-ddl-DYH95Qeg.js.map → boot-ddl-EsgqDkx0.js.map} +1 -1
- package/dist/branch-diff-CxE4VlxT.js +1396 -0
- package/dist/branch-diff-CxE4VlxT.js.map +1 -0
- package/dist/branch-diff-report-DpvVf7hR.js +48 -0
- package/dist/branch-diff-report-DpvVf7hR.js.map +1 -0
- package/dist/branching/branch-diff-report.d.ts +7 -0
- package/dist/branching/branch-diff.d.ts +115 -0
- package/dist/{cli-console-CQrQ0lAY.js → cli-console-f0k6_TsJ.js} +38 -5
- package/dist/cli-console-f0k6_TsJ.js.map +1 -0
- package/dist/cli-console.d.ts +17 -1
- package/dist/{cli-errors-DiA0XFIS.js → cli-errors-DsYyNYzd.js} +53 -2
- package/dist/cli-errors-DsYyNYzd.js.map +1 -0
- package/dist/cli.js +102 -122
- package/dist/cli.js.map +1 -1
- package/dist/{collections-schema-version-T6cGvrDi.js → collections-schema-version-D7bDI0PJ.js} +13 -9
- package/dist/collections-schema-version-D7bDI0PJ.js.map +1 -0
- package/dist/{deferred-index-build-BE0I6sYU.js → deferred-index-build-CqaOv1Xx.js} +3 -3
- package/dist/{deferred-index-build-BE0I6sYU.js.map → deferred-index-build-CqaOv1Xx.js.map} +1 -1
- package/dist/{doctor-BT--1kq3.js → doctor-DW_mlTzz.js} +81 -33
- package/dist/doctor-DW_mlTzz.js.map +1 -0
- package/dist/{ensure-collection-policies-BnGOURpG.js → ensure-collection-policies-DLWgabQ2.js} +5 -5
- package/dist/{ensure-collection-policies-BnGOURpG.js.map → ensure-collection-policies-DLWgabQ2.js.map} +1 -1
- package/dist/{ensure-collection-tables-Cet4650m.js → ensure-collection-tables-CFIRq8ni.js} +5 -5
- package/dist/{ensure-collection-tables-Cet4650m.js.map → ensure-collection-tables-CFIRq8ni.js.map} +1 -1
- package/dist/{ensure-tables-l_OgFQjN.js → ensure-tables-4nA-tfwi.js} +4 -4
- package/dist/{ensure-tables-l_OgFQjN.js.map → ensure-tables-4nA-tfwi.js.map} +1 -1
- package/dist/{enum-labels-kept-nheku1pB.js → enum-labels-kept-PcPW65xy.js} +2 -2
- package/dist/{enum-labels-kept-nheku1pB.js.map → enum-labels-kept-PcPW65xy.js.map} +1 -1
- package/dist/history/HistoryService.d.ts +7 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.es.js +226 -8
- package/dist/index.es.js.map +1 -1
- package/dist/{db-subcommands-BPoHCxJR.js → migrate-baseline-B-2Ef4Mv.js} +266 -41
- package/dist/migrate-baseline-B-2Ef4Mv.js.map +1 -0
- package/dist/{migrate-policy-stamps-3eqfhes6.js → migrate-policy-stamps-BQtFHApx.js} +5 -5
- package/dist/{migrate-policy-stamps-3eqfhes6.js.map → migrate-policy-stamps-BQtFHApx.js.map} +1 -1
- package/dist/{open-rebase-EmqVmS14.js → open-rebase-C8jQ6s5a.js} +292 -46
- package/dist/open-rebase-C8jQ6s5a.js.map +1 -0
- package/dist/open-rebase.d.ts +9 -1
- package/dist/{pg-error-utils-CFEnm2m9.js → pg-error-utils-DhiUTlVx.js} +2 -2
- package/dist/{pg-error-utils-CFEnm2m9.js.map → pg-error-utils-DhiUTlVx.js.map} +1 -1
- package/dist/{push-index-prebuild-COd7i6wm.js → push-index-prebuild-UTvB5pqH.js} +3 -3
- package/dist/{push-index-prebuild-COd7i6wm.js.map → push-index-prebuild-UTvB5pqH.js.map} +1 -1
- package/dist/push-kept-defaults-BGOlOqRQ.js +137 -0
- package/dist/push-kept-defaults-BGOlOqRQ.js.map +1 -0
- package/dist/{push-legacy-renames-CLgYYwPE.js → push-legacy-renames-BwaKi8xF.js} +3 -3
- package/dist/{push-legacy-renames-CLgYYwPE.js.map → push-legacy-renames-BwaKi8xF.js.map} +1 -1
- package/dist/{push-policy-plan-D2gpQsdv.js → push-policy-plan-DQ2tWceD.js} +2 -2
- package/dist/{push-policy-plan-D2gpQsdv.js.map → push-policy-plan-DQ2tWceD.js.map} +1 -1
- package/dist/{rls-enforcement-DDyNoOSy.js → rls-enforcement-BOHuHVlc.js} +42 -3
- package/dist/rls-enforcement-BOHuHVlc.js.map +1 -0
- package/dist/schema/destructive-sql.d.ts +16 -0
- package/dist/schema/doctor-cli.js +3 -3
- package/dist/schema/introspect-db.js +1 -1
- package/dist/schema/migration-freshness.d.ts +69 -0
- package/dist/schema/push-kept-defaults.d.ts +57 -0
- package/dist/schema/rls-bootstrap-sql.d.ts +47 -10
- package/dist/security/rls-enforcement.d.ts +18 -0
- package/dist/services/FetchService.d.ts +12 -0
- package/dist/services/channel-presence.d.ts +3 -1
- package/dist/services/nested-path.d.ts +16 -0
- package/dist/services/realtimeService.d.ts +18 -1
- package/dist/{websocket-BKZwsjmf.js → websocket-CKYc3V4N.js} +41 -12
- package/dist/websocket-CKYc3V4N.js.map +1 -0
- package/package.json +7 -7
- package/dist/BranchService-ClklxyAQ.js.map +0 -1
- package/dist/cli-console-CQrQ0lAY.js.map +0 -1
- package/dist/cli-errors-DiA0XFIS.js.map +0 -1
- package/dist/collections-schema-version-T6cGvrDi.js.map +0 -1
- package/dist/db-subcommands-BPoHCxJR.js.map +0 -1
- package/dist/doctor-BT--1kq3.js.map +0 -1
- package/dist/open-rebase-EmqVmS14.js.map +0 -1
- package/dist/rls-enforcement-DDyNoOSy.js.map +0 -1
- package/dist/websocket-BKZwsjmf.js.map +0 -1
package/dist/index.es.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
3
|
import { a as createReadReplicaConnection, c as pinSearchPath, i as createPostgresDatabaseConnection, l as poolMaxCeiling, o as defaultPoolTimeouts, r as createDirectDatabaseConnection, s as guardPoolAgainstDirtyRelease, t as cappedPoolMax } from "./connection-czvI7TZL.js";
|
|
4
|
-
import { $ as AuthenticatedPostgresBackendDriver, B as mfaFactors, C as PG_NOTIFY_MAX_PAYLOAD_BYTES, D as MemoryChannelBus, E as parseChannelBusPayload, F as createAuthSchema, G as recoveryCodesRelations, H as passwordResetTokens, I as magicLinkTokens, J as userIdentities, K as refreshTokens, L as magicLinkTokensRelations, O as frameByteLength, P as appConfig, Q as usersSchema, R as mfaChallenges, S as DEFAULT_BATCH_WINDOW_MS, T as parseChannelBusFrame, U as passwordResetTokensRelations, V as mfaFactorsRelations, W as recoveryCodes, X as users, Y as userIdentitiesRelations, Z as usersRelations, _ as parseRealtimeTuning, _t as listedColumnKeys, a as openPostgresDriver, at as isRoleSwitchingOptedOut, b as resolveChannelBusSetting, c as resolveSourceUrls, d as DEFAULT_RECOUNT_MIN_ROWS, dt as DrizzleConditionBuilder, et as CONNECTION_OWNER, f as PostgresRealtimeProvider, ft as PostgresConditionBuilder, g as parseMaxSubscriptionsPerSocket, gt as joinColumn, h as parseMaxSocketsPerUser, ht as getUnknownFilterFieldsMode, i as isScaffoldedLocalDatabase, it as effectiveSqlRole, k as isChannelBusInstance, l as PostgresCollectionRegistry, m as packEntityChanges, mt as escapeLikePattern, n as createPostgresBootstrapper, nt as RoleSwitchUnavailableError, o as postgresDriverConfigFromEnv, ot as sqlRoleRanAs, p as RealtimeService, pt as configureUnknownFilterFields, q as refreshTokensRelations, r as describeSchemaDriftCause, rt as branchParentDatabase, s as resolveDriftCheckName, t as openRebase, tt as PostgresBackendDriver, u as DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET, v as refetchConcurrencyFor, w as PostgresChannelBus, x as CHANNEL_BUS_NOTIFY_CHANNEL, y as createChannelBus, z as mfaChallengesRelations } from "./open-rebase-
|
|
5
|
-
import { a as reopenedDatabasesNotice, i as reopenClosedDatabases, r as BranchingUnsupportedError, t as BranchService } from "./BranchService-
|
|
6
|
-
import { i as ensureAppRole, r as detectConnectionPosture, t as REBASE_USER_ROLE } from "./rls-enforcement-
|
|
4
|
+
import { $ as AuthenticatedPostgresBackendDriver, B as mfaFactors, C as PG_NOTIFY_MAX_PAYLOAD_BYTES, D as MemoryChannelBus, E as parseChannelBusPayload, F as createAuthSchema, G as recoveryCodesRelations, H as passwordResetTokens, I as magicLinkTokens, J as userIdentities, K as refreshTokens, L as magicLinkTokensRelations, O as frameByteLength, P as appConfig, Q as usersSchema, R as mfaChallenges, S as DEFAULT_BATCH_WINDOW_MS, T as parseChannelBusFrame, U as passwordResetTokensRelations, V as mfaFactorsRelations, W as recoveryCodes, X as users, Y as userIdentitiesRelations, Z as usersRelations, _ as parseRealtimeTuning, _t as listedColumnKeys, a as openPostgresDriver, at as isRoleSwitchingOptedOut, b as resolveChannelBusSetting, c as resolveSourceUrls, d as DEFAULT_RECOUNT_MIN_ROWS, dt as DrizzleConditionBuilder, et as CONNECTION_OWNER, f as PostgresRealtimeProvider, ft as PostgresConditionBuilder, g as parseMaxSubscriptionsPerSocket, gt as joinColumn, h as parseMaxSocketsPerUser, ht as getUnknownFilterFieldsMode, i as isScaffoldedLocalDatabase, it as effectiveSqlRole, k as isChannelBusInstance, l as PostgresCollectionRegistry, m as packEntityChanges, mt as escapeLikePattern, n as createPostgresBootstrapper, nt as RoleSwitchUnavailableError, o as postgresDriverConfigFromEnv, ot as sqlRoleRanAs, p as RealtimeService, pt as configureUnknownFilterFields, q as refreshTokensRelations, r as describeSchemaDriftCause, rt as branchParentDatabase, s as resolveDriftCheckName, t as openRebase, tt as PostgresBackendDriver, u as DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET, v as refetchConcurrencyFor, w as PostgresChannelBus, x as CHANNEL_BUS_NOTIFY_CHANNEL, y as createChannelBus, z as mfaChallengesRelations } from "./open-rebase-C8jQ6s5a.js";
|
|
5
|
+
import { a as reopenedDatabasesNotice, i as reopenClosedDatabases, r as BranchingUnsupportedError, t as BranchService } from "./BranchService-CWBnDhcv.js";
|
|
6
|
+
import { i as ensureAppRole, r as detectConnectionPosture, t as REBASE_USER_ROLE } from "./rls-enforcement-BOHuHVlc.js";
|
|
7
|
+
import { n as planSchema } from "./plan-schema-Czf3q4-Q.js";
|
|
7
8
|
import { t as generateSchema } from "./generate-drizzle-schema-logic-CPzCRD-8.js";
|
|
9
|
+
import { a as mergeDatabaseBranch, i as diffDatabaseBranch, n as DEFAULT_BRANCH_DIFF_SAMPLE_LIMIT, t as DEFAULT_BRANCH_DIFF_MAX_ROWS } from "./branch-diff-CxE4VlxT.js";
|
|
8
10
|
import { t as DatabasePoolManager } from "./databasePoolManager-3stMNFL-.js";
|
|
9
11
|
import { t as main } from "./generate-drizzle-schema-DC0BOUpn.js";
|
|
10
|
-
import { a as createPostgresWebSocket, i as TOO_MANY_SOCKETS_CLOSE_CODE, n as PUBLIC_TYPES, o as isSocketRealtimeService, r as SESSION_ENDED_CLOSE_CODE, s as socketRealtimeMissing, t as ADMIN_PLANE_TYPES } from "./websocket-
|
|
11
|
-
import { A as buildBackupFilename, B as diagnoseRowSecurityDumpFailure, C as restoreDumpReplacing, D as resolvePgBinary, E as pgToolVariable, F as buildPgRestoreScriptArgs, G as parseDbNameFromUrl, H as joinStorageKey, I as buildPsqlScriptArgs, J as resolveConnectionString, K as parseDumpObjects, L as buildRowSecurityPgOptions, M as buildPgDumpallGlobalsArgs, N as buildPgRestoreArgs, O as selectBackupsToPrune, P as buildPgRestoreListArgs, R as checkToolServerCompatibility, S as restoreDump, T as validateDump, U as parseBackupDestination, V as globalsFileForDump, W as parseBackupTimestamp, X as splitGlobalsStatements, Y as serverVersionNumToMajor, Z as withDatabaseName, _ as listBackups, a as BackupToolError, b as readDumpObjects, d as detectToolMajor, f as discardPartialDump, g as getServerVersionMajor, h as findRestoreConflicts, i as parseRestoreLine, j as buildPgDumpArgs, k as CLEAN_LOCK_TIMEOUT, l as createDump, m as findCleanExtras, n as parseBackupLine, o as CleanRestoreFailed, p as ensureDatabaseExists, q as parsePgToolMajor, r as parseBackupsListLine, s as applyGlobals, u as describeUnreadableDump, v as preflight, w as uploadBackup, x as resetSharedSession, y as pruneBackups, z as cleanRestorePrelude } from "./backup-cli-
|
|
12
|
-
import { c as
|
|
12
|
+
import { a as createPostgresWebSocket, i as TOO_MANY_SOCKETS_CLOSE_CODE, n as PUBLIC_TYPES, o as isSocketRealtimeService, r as SESSION_ENDED_CLOSE_CODE, s as socketRealtimeMissing, t as ADMIN_PLANE_TYPES } from "./websocket-CKYc3V4N.js";
|
|
13
|
+
import { A as buildBackupFilename, B as diagnoseRowSecurityDumpFailure, C as restoreDumpReplacing, D as resolvePgBinary, E as pgToolVariable, F as buildPgRestoreScriptArgs, G as parseDbNameFromUrl, H as joinStorageKey, I as buildPsqlScriptArgs, J as resolveConnectionString, K as parseDumpObjects, L as buildRowSecurityPgOptions, M as buildPgDumpallGlobalsArgs, N as buildPgRestoreArgs, O as selectBackupsToPrune, P as buildPgRestoreListArgs, R as checkToolServerCompatibility, S as restoreDump, T as validateDump, U as parseBackupDestination, V as globalsFileForDump, W as parseBackupTimestamp, X as splitGlobalsStatements, Y as serverVersionNumToMajor, Z as withDatabaseName, _ as listBackups, a as BackupToolError, b as readDumpObjects, d as detectToolMajor, f as discardPartialDump, g as getServerVersionMajor, h as findRestoreConflicts, i as parseRestoreLine, j as buildPgDumpArgs, k as CLEAN_LOCK_TIMEOUT, l as createDump, m as findCleanExtras, n as parseBackupLine, o as CleanRestoreFailed, p as ensureDatabaseExists, q as parsePgToolMajor, r as parseBackupsListLine, s as applyGlobals, u as describeUnreadableDump, v as preflight, w as uploadBackup, x as resetSharedSession, y as pruneBackups, z as cleanRestorePrelude } from "./backup-cli-PoRG-zEq.js";
|
|
14
|
+
import { a as migrationVersionOf, c as isDriverDbSubcommand, l as backupActionOf, m as assertKnownFlags, n as compareMigrationVersions, u as assertKnownBranchFlags } from "./migrate-baseline-B-2Ef4Mv.js";
|
|
15
|
+
import { i as isGeneratedPolicyName } from "./policy-drift-X4A-xAzv.js";
|
|
13
16
|
import { logger } from "@rebasepro/server";
|
|
14
17
|
import fs from "fs";
|
|
15
18
|
//#region src/backup/backup-cron.ts
|
|
@@ -96,7 +99,7 @@ function createBackupCron(config) {
|
|
|
96
99
|
enabled: config.enabled ?? true,
|
|
97
100
|
timeoutSeconds: 3600,
|
|
98
101
|
async handler({ log }) {
|
|
99
|
-
const { createDump, discardPartialDump, pruneBackups, uploadBackup, validateDump } = await import("./backup-cli-
|
|
102
|
+
const { createDump, discardPartialDump, pruneBackups, uploadBackup, validateDump } = await import("./backup-cli-PoRG-zEq.js").then((n) => n.c);
|
|
100
103
|
const { destination } = config;
|
|
101
104
|
if (destination.kind !== "local" && !config.storage) throw new Error(`Backup destination is ${destination.kind} but no storage controller was provided. Pass the backend's configured StorageController to createBackupCron({ storage }).`);
|
|
102
105
|
log(`Starting backup of "${dbName}"…`);
|
|
@@ -258,6 +261,221 @@ function assertDbCommandLine(args) {
|
|
|
258
261
|
LINE_CHECKS[subcommand]([...args]);
|
|
259
262
|
}
|
|
260
263
|
//#endregion
|
|
261
|
-
|
|
264
|
+
//#region src/schema/migration-freshness.ts
|
|
265
|
+
/** Blank out `--` and `/* *\/` comments, leaving string and identifier literals alone. */
|
|
266
|
+
function stripComments(sql) {
|
|
267
|
+
let out = "";
|
|
268
|
+
let i = 0;
|
|
269
|
+
while (i < sql.length) {
|
|
270
|
+
const ch = sql[i];
|
|
271
|
+
if (ch === "'" || ch === "\"") {
|
|
272
|
+
const end = sql.indexOf(ch, i + 1);
|
|
273
|
+
const stop = end === -1 ? sql.length : end + 1;
|
|
274
|
+
out += sql.slice(i, stop);
|
|
275
|
+
i = stop;
|
|
276
|
+
} else if (ch === "-" && sql[i + 1] === "-") {
|
|
277
|
+
const end = sql.indexOf("\n", i);
|
|
278
|
+
i = end === -1 ? sql.length : end;
|
|
279
|
+
} else if (ch === "/" && sql[i + 1] === "*") {
|
|
280
|
+
const end = sql.indexOf("*/", i + 2);
|
|
281
|
+
i = end === -1 ? sql.length : end + 2;
|
|
282
|
+
} else if (ch === "$") {
|
|
283
|
+
const tag = /^\$[A-Za-z_]*\$/.exec(sql.slice(i))?.[0];
|
|
284
|
+
if (!tag) {
|
|
285
|
+
out += ch;
|
|
286
|
+
i++;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
const end = sql.indexOf(tag, i + tag.length);
|
|
290
|
+
const stop = end === -1 ? sql.length : end + tag.length;
|
|
291
|
+
out += sql.slice(i, stop);
|
|
292
|
+
i = stop;
|
|
293
|
+
} else {
|
|
294
|
+
out += ch;
|
|
295
|
+
i++;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return out;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Split on a separator at nesting depth zero, outside quotes and dollar quotes.
|
|
302
|
+
* `;` splits a file into statements, `,` a column list into its elements.
|
|
303
|
+
*/
|
|
304
|
+
function splitTopLevel(text, separator) {
|
|
305
|
+
const parts = [];
|
|
306
|
+
let depth = 0;
|
|
307
|
+
let start = 0;
|
|
308
|
+
let i = 0;
|
|
309
|
+
while (i < text.length) {
|
|
310
|
+
const ch = text[i];
|
|
311
|
+
if (ch === "'" || ch === "\"") {
|
|
312
|
+
const end = text.indexOf(ch, i + 1);
|
|
313
|
+
i = end === -1 ? text.length : end + 1;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (ch === "$") {
|
|
317
|
+
const tag = /^\$[A-Za-z_]*\$/.exec(text.slice(i))?.[0];
|
|
318
|
+
if (tag) {
|
|
319
|
+
const end = text.indexOf(tag, i + tag.length);
|
|
320
|
+
i = end === -1 ? text.length : end + tag.length;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (ch === "(") depth++;
|
|
325
|
+
else if (ch === ")") depth--;
|
|
326
|
+
else if (ch === separator && depth === 0) {
|
|
327
|
+
parts.push(text.slice(start, i));
|
|
328
|
+
start = i + 1;
|
|
329
|
+
}
|
|
330
|
+
i++;
|
|
331
|
+
}
|
|
332
|
+
parts.push(text.slice(start));
|
|
333
|
+
return parts.map((p) => p.trim()).filter((p) => p.length > 0);
|
|
334
|
+
}
|
|
335
|
+
/** One identifier: `"Quoted"` keeps its case, a bare word folds to lower case as Postgres folds it. */
|
|
336
|
+
var IDENT = String.raw`(?:"(?:[^"]|"")+"|[A-Za-z_][A-Za-z0-9_$]*)`;
|
|
337
|
+
/** An optionally schema-qualified name. */
|
|
338
|
+
var QUALIFIED = String.raw`${IDENT}(?:\s*\.\s*${IDENT})?`;
|
|
339
|
+
function unquote(identifier) {
|
|
340
|
+
const trimmed = identifier.trim();
|
|
341
|
+
return trimmed.startsWith("\"") ? trimmed.slice(1, -1).replace(/""/g, "\"") : trimmed.toLowerCase();
|
|
342
|
+
}
|
|
343
|
+
/** `"public"."posts"` → `public.posts`; an unqualified name is in `public`. */
|
|
344
|
+
function qualifiedName(name) {
|
|
345
|
+
const [first = "", second] = name.match(new RegExp(IDENT, "g")) ?? [];
|
|
346
|
+
return second !== void 0 ? `${unquote(first)}.${unquote(second)}` : `public.${unquote(first)}`;
|
|
347
|
+
}
|
|
348
|
+
/** Words that open a table constraint rather than a column, inside `CREATE TABLE ( … )`. */
|
|
349
|
+
var TABLE_CONSTRAINT = /^(CONSTRAINT|PRIMARY|UNIQUE|FOREIGN|CHECK|EXCLUDE|LIKE)\b/i;
|
|
350
|
+
var CREATE_TABLE = new RegExp(String.raw`^CREATE\s+(?:UNLOGGED\s+)?TABLE\s+(IF\s+NOT\s+EXISTS\s+)?(${QUALIFIED})\s*\(([\s\S]*)\)[^)]*$`, "i");
|
|
351
|
+
var ALTER_TABLE = new RegExp(String.raw`^ALTER\s+TABLE\s+(?:IF\s+EXISTS\s+)?(?:ONLY\s+)?(${QUALIFIED})\s+([\s\S]*)$`, "i");
|
|
352
|
+
var DROP_TABLE = /^DROP\s+TABLE\s+(?:IF\s+EXISTS\s+)?([\s\S]*?)(?:\s+(?:CASCADE|RESTRICT))?$/i;
|
|
353
|
+
var ADD_COLUMN = new RegExp(String.raw`^ADD\s+(?:COLUMN\s+)?(IF\s+NOT\s+EXISTS\s+)?(${IDENT})`, "i");
|
|
354
|
+
var DROP_COLUMN = new RegExp(String.raw`^DROP\s+(?:COLUMN\s+)?(?:IF\s+EXISTS\s+)?(${IDENT})`, "i");
|
|
355
|
+
var RENAME_COLUMN = new RegExp(String.raw`^RENAME\s+(?:COLUMN\s+)?(${IDENT})\s+TO\s+(${IDENT})$`, "i");
|
|
356
|
+
var RENAME_TABLE = new RegExp(String.raw`^RENAME\s+TO\s+(${IDENT})$`, "i");
|
|
357
|
+
/**
|
|
358
|
+
* The tables and columns the migrations leave behind, replayed in order.
|
|
359
|
+
*
|
|
360
|
+
* Exported for its tests.
|
|
361
|
+
*/
|
|
362
|
+
function replayMigrationTables(migrations) {
|
|
363
|
+
const tables = /* @__PURE__ */ new Map();
|
|
364
|
+
const tableOf = (name) => {
|
|
365
|
+
let table = tables.get(name);
|
|
366
|
+
if (!table) {
|
|
367
|
+
table = {
|
|
368
|
+
columns: /* @__PURE__ */ new Set(),
|
|
369
|
+
managed: /* @__PURE__ */ new Set()
|
|
370
|
+
};
|
|
371
|
+
tables.set(name, table);
|
|
372
|
+
}
|
|
373
|
+
return table;
|
|
374
|
+
};
|
|
375
|
+
for (const migration of migrations) for (const raw of splitTopLevel(stripComments(migration), ";")) {
|
|
376
|
+
const statement = raw.replace(/\s+/g, " ").trim();
|
|
377
|
+
const created = CREATE_TABLE.exec(statement);
|
|
378
|
+
if (created) {
|
|
379
|
+
const table = tableOf(qualifiedName(created[2]));
|
|
380
|
+
const managed = !created[1];
|
|
381
|
+
for (const element of splitTopLevel(created[3], ",")) {
|
|
382
|
+
if (TABLE_CONSTRAINT.test(element)) continue;
|
|
383
|
+
const column = new RegExp(`^${IDENT}`).exec(element)?.[0];
|
|
384
|
+
if (!column) continue;
|
|
385
|
+
table.columns.add(unquote(column));
|
|
386
|
+
if (managed) table.managed.add(unquote(column));
|
|
387
|
+
}
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
const altered = ALTER_TABLE.exec(statement);
|
|
391
|
+
if (altered) {
|
|
392
|
+
const name = qualifiedName(altered[1]);
|
|
393
|
+
for (const action of splitTopLevel(altered[2], ",")) {
|
|
394
|
+
const renamedTable = RENAME_TABLE.exec(action);
|
|
395
|
+
if (renamedTable) {
|
|
396
|
+
const existing = tables.get(name);
|
|
397
|
+
tables.delete(name);
|
|
398
|
+
const target = `${name.split(".")[0]}.${unquote(renamedTable[1])}`;
|
|
399
|
+
if (existing) tables.set(target, existing);
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
const renamed = RENAME_COLUMN.exec(action);
|
|
403
|
+
if (renamed) {
|
|
404
|
+
const table = tableOf(name);
|
|
405
|
+
const from = unquote(renamed[1]);
|
|
406
|
+
const to = unquote(renamed[2]);
|
|
407
|
+
table.columns.delete(from);
|
|
408
|
+
table.columns.add(to);
|
|
409
|
+
if (table.managed.delete(from)) table.managed.add(to);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
const added = ADD_COLUMN.exec(action);
|
|
413
|
+
if (added && !/^ADD\s+(CONSTRAINT|PRIMARY|UNIQUE|FOREIGN|CHECK|EXCLUDE)\b/i.test(action)) {
|
|
414
|
+
const table = tableOf(name);
|
|
415
|
+
const column = unquote(added[2]);
|
|
416
|
+
table.columns.add(column);
|
|
417
|
+
if (!added[1]) table.managed.add(column);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
const dropped = DROP_COLUMN.exec(action);
|
|
421
|
+
if (dropped && !/^DROP\s+(CONSTRAINT|DEFAULT|NOT|EXPRESSION|IDENTITY)\b/i.test(action)) {
|
|
422
|
+
const table = tableOf(name);
|
|
423
|
+
const column = unquote(dropped[1]);
|
|
424
|
+
table.columns.delete(column);
|
|
425
|
+
table.managed.delete(column);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const droppedTables = DROP_TABLE.exec(statement);
|
|
431
|
+
if (droppedTables) for (const name of splitTopLevel(droppedTables[1], ",")) tables.delete(qualifiedName(name));
|
|
432
|
+
}
|
|
433
|
+
return tables;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* What the collections declare that the migrations do not build, and what the
|
|
437
|
+
* migrations still build that the collections dropped.
|
|
438
|
+
*
|
|
439
|
+
* `migrations` are the contents of every `.sql` file in the migrations
|
|
440
|
+
* directory, in file-name order — Atlas's order. An empty list is a project
|
|
441
|
+
* with no migrations at all, for which everything is missing; whether that is a
|
|
442
|
+
* problem (a project on `db push` has none by design) is the caller's call.
|
|
443
|
+
*
|
|
444
|
+
* Throws what {@link planSchema} throws for a configuration it cannot plan.
|
|
445
|
+
*/
|
|
446
|
+
function findUnmigratedSchema(collections, migrations) {
|
|
447
|
+
const plan = planSchema(collections);
|
|
448
|
+
const migrated = replayMigrationTables(migrations);
|
|
449
|
+
const changes = [];
|
|
450
|
+
for (const table of plan.tables) {
|
|
451
|
+
const slug = table.slug;
|
|
452
|
+
const existing = migrated.get(table.qualified);
|
|
453
|
+
if (!existing) {
|
|
454
|
+
changes.push({
|
|
455
|
+
kind: "missing-table",
|
|
456
|
+
table: table.qualified,
|
|
457
|
+
...slug && { slug }
|
|
458
|
+
});
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (table.auth) continue;
|
|
462
|
+
const planned = new Set(table.columns.filter((column) => column.source.kind !== "search").map((column) => column.column));
|
|
463
|
+
for (const column of planned) if (!existing.columns.has(column)) changes.push({
|
|
464
|
+
kind: "missing-column",
|
|
465
|
+
table: table.qualified,
|
|
466
|
+
column,
|
|
467
|
+
...slug && { slug }
|
|
468
|
+
});
|
|
469
|
+
for (const column of existing.managed) if (!planned.has(column)) changes.push({
|
|
470
|
+
kind: "removed-column",
|
|
471
|
+
table: table.qualified,
|
|
472
|
+
column,
|
|
473
|
+
...slug && { slug }
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
return changes;
|
|
477
|
+
}
|
|
478
|
+
//#endregion
|
|
479
|
+
export { ADMIN_PLANE_TYPES, AuthenticatedPostgresBackendDriver, BackupToolError, BranchService, BranchingUnsupportedError, CHANNEL_BUS_NOTIFY_CHANNEL, CLEAN_LOCK_TIMEOUT, CONNECTION_OWNER, CleanRestoreFailed, DEFAULT_BATCH_WINDOW_MS, DEFAULT_BRANCH_DIFF_MAX_ROWS, DEFAULT_BRANCH_DIFF_SAMPLE_LIMIT, DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET, DEFAULT_RECOUNT_MIN_ROWS, DatabasePoolManager, DrizzleConditionBuilder, MemoryChannelBus, PG_NOTIFY_MAX_PAYLOAD_BYTES, PUBLIC_TYPES, PostgresBackendDriver, PostgresChannelBus, PostgresCollectionRegistry, PostgresConditionBuilder, PostgresRealtimeProvider, REBASE_USER_ROLE, RealtimeService, RoleSwitchUnavailableError, SESSION_ENDED_CLOSE_CODE, TOO_MANY_SOCKETS_CLOSE_CODE, appConfig, applyGlobals, assertDbCommandLine, backupCronConfigFromEnv, branchParentDatabase, buildBackupFilename, buildPgDumpArgs, buildPgDumpallGlobalsArgs, buildPgRestoreArgs, buildPgRestoreListArgs, buildPgRestoreScriptArgs, buildPsqlScriptArgs, buildRowSecurityPgOptions, cappedPoolMax, checkToolServerCompatibility, cleanRestorePrelude, compareMigrationVersions, configureUnknownFilterFields, createAuthSchema, createBackupCron, createChannelBus, createDirectDatabaseConnection, createDump, createPostgresAdapter, createPostgresBootstrapper, createPostgresDatabaseConnection, createPostgresWebSocket, createReadReplicaConnection, defaultPoolTimeouts, describeSchemaDriftCause, describeUnreadableDump, detectConnectionPosture, detectToolMajor, diagnoseRowSecurityDumpFailure, diffDatabaseBranch, discardPartialDump, effectiveSqlRole, ensureAppRole, ensureDatabaseExists, escapeLikePattern, findCleanExtras, findRestoreConflicts, findUnmigratedSchema, frameByteLength, generateSchema, getServerVersionMajor, getUnknownFilterFieldsMode, globalsFileForDump, guardPoolAgainstDirtyRelease, isChannelBusInstance, isGeneratedPolicyName, isRoleSwitchingOptedOut, isScaffoldedLocalDatabase, isSocketRealtimeService, joinColumn, joinStorageKey, listBackups, listedColumnKeys, magicLinkTokens, magicLinkTokensRelations, main, mergeDatabaseBranch, mfaChallenges, mfaChallengesRelations, mfaFactors, mfaFactorsRelations, migrationVersionOf, openPostgresDriver, openRebase, packEntityChanges, parseBackupDestination, parseBackupTimestamp, parseChannelBusFrame, parseChannelBusPayload, parseDbNameFromUrl, parseDumpObjects, parseMaxSocketsPerUser, parseMaxSubscriptionsPerSocket, parsePgToolMajor, parseRealtimeTuning, passwordResetTokens, passwordResetTokensRelations, pinSearchPath, poolMaxCeiling, postgresDriverConfigFromEnv, preflight, pruneBackups, readDumpObjects, recoveryCodes, recoveryCodesRelations, refetchConcurrencyFor, refreshTokens, refreshTokensRelations, reopenClosedDatabases, reopenedDatabasesNotice, resetSharedSession, resolveChannelBusSetting, resolveConnectionString, resolveDriftCheckName, resolvePgBinary, resolveSourceUrls, restoreDump, restoreDumpReplacing, selectBackupsToPrune, serverVersionNumToMajor, socketRealtimeMissing, splitGlobalsStatements, sqlRoleRanAs, uploadBackup, userIdentities, userIdentitiesRelations, users, usersRelations, usersSchema, validateDump, withDatabaseName };
|
|
262
480
|
|
|
263
481
|
//# sourceMappingURL=index.es.js.map
|
package/dist/index.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","names":[],"sources":["../src/backup/backup-cron.ts","../src/PostgresAdapter.ts","../src/db-command-line.ts"],"sourcesContent":["/**\n * Scheduled backups, wired into the server cron system.\n *\n * A backend enables nightly (or custom-schedule) backups by dropping a cron\n * file that default-exports {@link createBackupCron}. The heavy lifting —\n * dump, upload, prune — reuses the same primitives as the CLI so behaviour\n * is identical between a manual `rebase db backup` and an automated run.\n */\nimport fs from \"fs\";\nimport type { CronJobDefinition } from \"@rebasepro/types\";\nimport type { StorageController } from \"@rebasepro/server\";\nimport { logger } from \"@rebasepro/server\";\nimport { pgToolVariable, resolvePgBinary, type PgTool } from \"./pg-binary\";\nimport {\n BackupDestination,\n parseBackupDestination,\n parseDbNameFromUrl,\n resolveConnectionString\n} from \"./pg-tools\";\n// NOTE: `./backup-service` pulls in `execa` (ESM-only). It is imported\n// lazily inside the handler so the pure `backupCronConfigFromEnv` parser can\n// be unit-tested under CommonJS without loading it.\n\nexport interface BackupCronConfig {\n /** Cron expression, e.g. `\"0 3 * * *\"` for 03:00 daily. */\n schedule: string;\n /** Postgres connection string. Defaults to `DATABASE_URL`. */\n connectionString: string;\n /** Where backups are written: a local path or an `s3://` / `gs://` URL. */\n destination: BackupDestination;\n /**\n * Storage controller used for `s3`/`gcs` destinations. Reuse the one the\n * backend already configured (S3/GCS/Local StorageController). Not needed\n * for local destinations.\n */\n storage?: StorageController;\n /** Delete backups older than this many days. `0` disables pruning. */\n retentionDays?: number;\n /** Always keep at least this many recent backups regardless of age. */\n keepMinimum?: number;\n /**\n * Schemas to exclude from the dump. Defaults to none, the same as\n * `rebase db backup` — see {@link createBackupCron} for why `rebase` in\n * particular must stay in.\n */\n excludeSchemas?: string[];\n /** Cron job display name. */\n name?: string;\n /** Whether the job is enabled. */\n enabled?: boolean;\n}\n\nexport interface EnvResolution {\n /** Resolved config, present when a schedule is configured. */\n config?: Omit<BackupCronConfig, \"storage\">;\n /** True when `BACKUP_SCHEDULE` is unset — the cron should be a no-op. */\n disabled?: boolean;\n /** Human-readable reason the config could not be built. */\n error?: string;\n}\n\n/**\n * Build backup-cron config purely from environment variables.\n * Recognised keys:\n * - `BACKUP_SCHEDULE` cron expression (required to enable)\n * - `BACKUP_DESTINATION` local path or `s3://` / `gs://` URL (required)\n * - `BACKUP_RETENTION_DAYS` integer days (optional)\n * - `BACKUP_KEEP_MINIMUM` integer count (optional)\n * - `DATABASE_URL` connection string\n *\n * Pure and side-effect free so it can be unit-tested without a database.\n */\nexport function backupCronConfigFromEnv(env: Record<string, string | undefined>): EnvResolution {\n const schedule = env.BACKUP_SCHEDULE?.trim();\n if (!schedule) {\n return { disabled: true };\n }\n\n const connectionString = resolveConnectionString(env);\n if (!connectionString) {\n return { error: \"BACKUP_SCHEDULE is set but DATABASE_URL is not configured.\" };\n }\n\n const destinationRaw = env.BACKUP_DESTINATION?.trim();\n if (!destinationRaw) {\n return { error: \"BACKUP_SCHEDULE is set but BACKUP_DESTINATION is not configured.\" };\n }\n const destination = parseBackupDestination(destinationRaw);\n\n const retentionDays = parseOptionalInt(env.BACKUP_RETENTION_DAYS);\n if (retentionDays === \"invalid\") {\n return { error: `BACKUP_RETENTION_DAYS must be an integer, got \"${env.BACKUP_RETENTION_DAYS}\".` };\n }\n const keepMinimum = parseOptionalInt(env.BACKUP_KEEP_MINIMUM);\n if (keepMinimum === \"invalid\") {\n return { error: `BACKUP_KEEP_MINIMUM must be an integer, got \"${env.BACKUP_KEEP_MINIMUM}\".` };\n }\n\n return {\n config: {\n schedule,\n connectionString,\n destination,\n retentionDays: retentionDays ?? undefined,\n keepMinimum: keepMinimum ?? undefined\n }\n };\n}\n\nfunction parseOptionalInt(value: string | undefined): number | null | \"invalid\" {\n if (value === undefined || value.trim() === \"\") return null;\n const n = Number(value);\n if (!Number.isInteger(n) || n < 0) return \"invalid\";\n return n;\n}\n\n/**\n * Create a {@link CronJobDefinition} that dumps the database, uploads the\n * result to the configured destination, and prunes old backups. Object\n * destinations require {@link BackupCronConfig.storage}.\n *\n * The dump excludes nothing unless `excludeSchemas` says otherwise, and that\n * default is not this function's to set: the option goes to `createDump`\n * untouched, so `buildPgDumpArgs` decides for the cron and `rebase db backup`\n * alike. The cron used to carry its own default of `[\"rebase\"]`, meant to\n * leave out Atlas's revision table, and it left out a lot more. `rebase` is\n * where the framework keeps\n * everything that is not a collection: the auth tables (`users`, identities,\n * refresh tokens, MFA factors, recovery codes, `app_config`, `schema_meta`),\n * API keys, record history, the job queue, cron logs, channel history,\n * branches and idempotency keys. It also holds the functions that every\n * generated RLS policy and CDC trigger calls (`rebase.uid()`,\n * `rebase.roles()`, `rebase.rebase_cdc_notify()`).\n *\n * None of that can be left for boot to rebuild. Boot recreates the tables\n * and functions, but never the rows in them. And a dump that has a policy\n * or trigger on a `public` table without the function it calls cannot be\n * restored into an empty database: `pg_restore --exit-on-error` stops at\n * the first such statement, and every collection's generated policies call\n * `rebase.uid()`. So every scheduled backup lost every user account, and\n * could not be restored anyway, while `rebase db backup` took a complete\n * one. The revision table belongs in a backup too: without it, a restored\n * database has tables but no record of the migrations that made them.\n */\nexport function createBackupCron(config: BackupCronConfig): CronJobDefinition {\n const dbName = parseDbNameFromUrl(config.connectionString) ?? \"database\";\n if (config.enabled ?? true) warnOfMissingTools(config.name ?? \"Scheduled database backup\");\n\n const job: CronJobDefinition = {\n name: config.name ?? \"Scheduled database backup\",\n schedule: config.schedule,\n description: \"Dumps the Postgres database and uploads it to the configured backup destination.\",\n enabled: config.enabled ?? true,\n // Backups of a large database can take a while; allow up to an hour.\n timeoutSeconds: 3600,\n async handler({ log }) {\n const { createDump, discardPartialDump, pruneBackups, uploadBackup, validateDump } =\n await import(\"./backup-service\");\n const { destination } = config;\n\n if (destination.kind !== \"local\" && !config.storage) {\n throw new Error(\n `Backup destination is ${destination.kind} but no storage controller was provided. ` +\n \"Pass the backend's configured StorageController to createBackupCron({ storage }).\"\n );\n }\n\n log(`Starting backup of \"${dbName}\"…`);\n const outDir = destination.kind === \"local\" ? destination.path : undefined;\n const dump = await createDump({\n connectionString: config.connectionString,\n dbName,\n outDir,\n excludeSchemas: config.excludeSchemas\n });\n log(`Dump created: ${dump.fileName} (${formatBytes(dump.sizeBytes)})`);\n\n // Validate BEFORE pruning: a corrupt-but-exit-0 dump must never be\n // the reason the last good backup gets deleted.\n const check = await validateDump(dump.localFile);\n if (!check.ok) {\n // Discarded for every destination, the roles sidecar with it.\n // On a local one the file *is* the backup: left behind, it\n // keeps a valid backup's name, and retention — which ranks by\n // timestamp and never opens a file — counts it among the\n // newest it keeps while pruning a real backup beneath it.\n if (dump.globalsFile) discardPartialDump(dump.globalsFile);\n discardPartialDump(dump.localFile);\n throw new Error(`New backup failed validation — skipping upload and pruning to protect existing backups. ${check.reason}`);\n }\n\n let storedKey = dump.localFile;\n try {\n if (destination.kind !== \"local\") {\n const uploaded = await uploadBackup(config.storage!, dump.localFile, destination);\n storedKey = uploaded.storageUrl;\n log(`Uploaded to ${uploaded.storageUrl}`);\n // Upload the roles sidecar so a restore can recreate the\n // roles the dump's GRANT/RLS statements depend on.\n if (dump.globalsFile && fs.existsSync(dump.globalsFile)) {\n const g = await uploadBackup(config.storage!, dump.globalsFile, destination);\n log(`Uploaded roles sidecar to ${g.storageUrl}`);\n }\n }\n } finally {\n // For object-storage destinations the local dump was a temp\n // file — remove it once uploaded (or on failure).\n if (destination.kind !== \"local\" && fs.existsSync(dump.localFile)) {\n fs.unlinkSync(dump.localFile);\n }\n if (dump.globalsFile && destination.kind !== \"local\" && fs.existsSync(dump.globalsFile)) {\n fs.unlinkSync(dump.globalsFile);\n }\n }\n\n let pruned: string[] = [];\n if (config.retentionDays && config.retentionDays > 0) {\n pruned = await pruneBackups(\n destination,\n { retentionDays: config.retentionDays, keepMinimum: config.keepMinimum },\n config.storage\n );\n if (pruned.length > 0) {\n log(`Pruned ${pruned.length} backup(s) older than ${config.retentionDays} day(s).`);\n }\n }\n\n return {\n backup: storedKey,\n sizeBytes: dump.sizeBytes,\n pruned: pruned.length\n };\n }\n };\n\n // How the Backups panel finds this job among the project's crons: its id is\n // the cron file's name and its display name is configurable. A registry\n // symbol, so `@rebasepro/server` reads it without importing this package\n // (it must not), and non-enumerable, so a spread or a JSON dump of the\n // definition is unchanged. See `readBackupSchedule` in the server package.\n Object.defineProperty(job, Symbol.for(\"rebase.backupCron\"), { value: true });\n return job;\n}\n\n/**\n * Say at registration, not at 03:00, that the job cannot run.\n *\n * Each run needs `pg_dump` for the dump and `pg_dumpall` for the roles\n * sidecar. Without them every run fails with \"Could not find the 'pg_dump'\n * binary\", and that was the first an operator heard of it — a runtime image\n * once shipped without the client tools, and every nightly backup failed.\n */\nfunction warnOfMissingTools(jobName: string): void {\n const needed: PgTool[] = [\"pg_dump\", \"pg_dumpall\"];\n const missing = needed.filter(tool => !resolvePgBinary(tool));\n if (missing.length === 0) return;\n logger.error(\n `\"${jobName}\" is scheduled, but ${missing.map(tool => `'${tool}'`).join(\" and \")} ` +\n `${missing.length > 1 ? \"were\" : \"was\"} not found on this machine, so every run will fail. ` +\n \"Install the PostgreSQL client tools (the same major version as the server, or newer), \" +\n `or set ${missing.map(pgToolVariable).join(\" / \")} to the binary.`\n );\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n","import { DatabaseAdapter, InitializedDriver, RealtimeProvider, DataDriver, DatabaseAdmin, BootstrappedAuth } from \"@rebasepro/types\";\nimport { createPostgresBootstrapper } from \"./PostgresBootstrapper\";\nimport type { PostgresDriverConfig } from \"./PostgresBootstrapper\";\n\n/**\n * Creates a Postgres database adapter for Rebase.\n */\nexport function createPostgresAdapter(pgConfig: PostgresDriverConfig): DatabaseAdapter {\n const bootstrapper = createPostgresBootstrapper(pgConfig);\n\n return {\n type: bootstrapper.type,\n\n async initializeDriver(config) {\n return bootstrapper.initializeDriver(config);\n },\n\n async initializeRealtime(driverResult) {\n if (bootstrapper.initializeRealtime) {\n return bootstrapper.initializeRealtime({}, driverResult);\n }\n return undefined;\n },\n\n async initializeAuth(config, driverResult) {\n if (bootstrapper.initializeAuth) {\n return bootstrapper.initializeAuth(config, driverResult);\n }\n return undefined;\n },\n\n async initializeHistory(config, driverResult) {\n if (bootstrapper.initializeHistory) {\n return bootstrapper.initializeHistory(config, driverResult);\n }\n return undefined;\n },\n\n // `adapter` and `limits` are forwarded for the same reason the schema\n // hooks below are: dropping an argument here is invisible at the call\n // site and silent at runtime. `adapter` decided whether the realtime\n // socket authenticates at all — without it, a server whose auth comes\n // from an AuthAdapter fell back to \"is a jwtSecret set?\", answered no,\n // and admitted every client. `limits` are the data API's body and rate\n // limits; dropped, the socket has neither.\n initializeWebsockets(server, realtimeService, driver, config, adapter, limits) {\n if (bootstrapper.initializeWebsockets) {\n return bootstrapper.initializeWebsockets(server, realtimeService, driver, config, adapter, limits);\n }\n },\n\n // Forwarded so the boot-time schema/RLS provisioning is reachable when\n // this adapter is wrapped back into a bootstrapper. Omitting either left\n // a managed tenant with no tables (500 on every data route) or tables\n // but no policies (401 on every read) — the create step never ran.\n verifyConnection: bootstrapper.verifyConnection\n ? (driverResult) => bootstrapper.verifyConnection!(driverResult)\n : undefined,\n\n ensureCollectionSchema: bootstrapper.ensureCollectionSchema\n ? (collections, driverResult, log) =>\n bootstrapper.ensureCollectionSchema!(collections, driverResult, log)\n : undefined,\n\n // Same rule, and it bit at once: the method existed on the bootstrapper,\n // this wrapper did not forward it, and a second database booted with\n // tables and no helper functions — every policy on it silently failed\n // to create. The adapter type is where a forgotten forward should fail\n // to compile; `adapter-forwarding.test.ts` sweeps for exactly this.\n ensureRlsRuntime: bootstrapper.ensureRlsRuntime\n ? (driverResult) => bootstrapper.ensureRlsRuntime!(driverResult)\n : undefined,\n\n ensureCollectionPolicies: bootstrapper.ensureCollectionPolicies\n ? (collections, driverResult, log) =>\n bootstrapper.ensureCollectionPolicies!(collections, driverResult, log)\n : undefined,\n\n finalizeSecurityPosture: bootstrapper.finalizeSecurityPosture\n ? (driverResult) => bootstrapper.finalizeSecurityPosture!(driverResult)\n : undefined,\n\n // Same forwarding rule, third instance. Dropping these is not a type\n // error and not a runtime error: the stamp is simply never written and\n // never read, so a split deployment loses the only thing that would tell\n // it a unit is serving against a schema it was not built for — and a\n // check that is off looks exactly like a check that passed. This one was\n // in fact dropped on the first attempt, and the e2e caught it.\n readCollectionsSchemaVersion: bootstrapper.readCollectionsSchemaVersion\n ? (driverResult) => bootstrapper.readCollectionsSchemaVersion!(driverResult)\n : undefined,\n\n stampCollectionsSchemaVersion: bootstrapper.stampCollectionsSchemaVersion\n ? (version, driverResult) => bootstrapper.stampCollectionsSchemaVersion!(version, driverResult)\n : undefined,\n\n getAdmin(driverResult) {\n if (bootstrapper.getAdmin) {\n return bootstrapper.getAdmin(driverResult);\n }\n return undefined;\n }\n };\n}\n","/**\n * Every `rebase db …` line this driver answers, judged without connecting to\n * anything.\n *\n * The driver refuses a flag a command does not take, but it is spawned only\n * once the CLI has a database for it, and getting one means starting one: the\n * remembered compose container, or the managed PGlite daemon. So `rebase db\n * push --dry-rn` booted the database and then said the flag did not exist.\n * The CLI runs this first, from the driver's own code, so the answer it gives\n * before starting anything and the one the driver would give after are one\n * check, not two lists to keep in step.\n *\n * Each entry calls the parse its command runs itself, never a copy of it.\n */\nimport { assertKnownFlags } from \"./cli-flags\";\nimport { assertKnownBranchFlags } from \"./branch-argv\";\nimport { backupActionOf } from \"./backup-argv\";\nimport { parseBackupLine, parseBackupsListLine, parseRestoreLine } from \"./backup/backup-cli\";\nimport { isDriverDbSubcommand, type DriverDbSubcommand } from \"./db-subcommands\";\n\n/**\n * The check each subcommand's line gets. A `Record` over the union, so a\n * subcommand added to `DRIVER_DB_SUBCOMMANDS` does not compile until it says\n * how its line is judged.\n */\nconst LINE_CHECKS: Record<DriverDbSubcommand, (args: string[]) => void> = {\n push: args => assertKnownFlags(\"db\", \"push\", args),\n generate: args => assertKnownFlags(\"db\", \"generate\", args),\n migrate: args => assertKnownFlags(\"db\", \"migrate\", args),\n // The action's own table; an action the table does not list is the\n // dispatch's to answer, in its own words.\n branch: (args) => {\n if (args[2] !== undefined) assertKnownBranchFlags(args[2], args.slice(3));\n },\n backup: (args) => {\n if (backupActionOf(args) === \"list\") parseBackupsListLine(args);\n else parseBackupLine(args);\n },\n restore: (args) => { parseRestoreLine(args); },\n backups: (args) => {\n if (args[2] === \"list\") parseBackupsListLine(args);\n }\n};\n\n/**\n * Throw what the driver would say of a `rebase db …` line it refuses on its\n * flags, before it would connect: an option the command does not take, and\n * the checks `db backup` and `db restore` make of their own line (a stray\n * word, a pattern that leaves out the rebase schema).\n *\n * Reads no environment and opens nothing, so it is safe to run before a\n * database exists.\n *\n * @param args the driver's line: `[\"db\", \"<subcommand>\", …]`, without the\n * CLI's own words in front. A line for a subcommand this driver does not\n * answer passes, for the dispatch to refuse.\n */\nexport function assertDbCommandLine(args: readonly string[]): void {\n const [domain, subcommand] = args;\n if (domain !== \"db\" || !isDriverDbSubcommand(subcommand)) return;\n // `--help` is answered before any check, by the CLI and by the dispatch.\n if (args.includes(\"--help\") || args.includes(\"-h\")) return;\n LINE_CHECKS[subcommand]([...args]);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.es.js","names":[],"sources":["../src/backup/backup-cron.ts","../src/PostgresAdapter.ts","../src/db-command-line.ts","../src/schema/migration-freshness.ts"],"sourcesContent":["/**\n * Scheduled backups, wired into the server cron system.\n *\n * A backend enables nightly (or custom-schedule) backups by dropping a cron\n * file that default-exports {@link createBackupCron}. The heavy lifting —\n * dump, upload, prune — reuses the same primitives as the CLI so behaviour\n * is identical between a manual `rebase db backup` and an automated run.\n */\nimport fs from \"fs\";\nimport type { CronJobDefinition } from \"@rebasepro/types\";\nimport type { StorageController } from \"@rebasepro/server\";\nimport { logger } from \"@rebasepro/server\";\nimport { pgToolVariable, resolvePgBinary, type PgTool } from \"./pg-binary\";\nimport {\n BackupDestination,\n parseBackupDestination,\n parseDbNameFromUrl,\n resolveConnectionString\n} from \"./pg-tools\";\n// NOTE: `./backup-service` pulls in `execa` (ESM-only). It is imported\n// lazily inside the handler so the pure `backupCronConfigFromEnv` parser can\n// be unit-tested under CommonJS without loading it.\n\nexport interface BackupCronConfig {\n /** Cron expression, e.g. `\"0 3 * * *\"` for 03:00 daily. */\n schedule: string;\n /** Postgres connection string. Defaults to `DATABASE_URL`. */\n connectionString: string;\n /** Where backups are written: a local path or an `s3://` / `gs://` URL. */\n destination: BackupDestination;\n /**\n * Storage controller used for `s3`/`gcs` destinations. Reuse the one the\n * backend already configured (S3/GCS/Local StorageController). Not needed\n * for local destinations.\n */\n storage?: StorageController;\n /** Delete backups older than this many days. `0` disables pruning. */\n retentionDays?: number;\n /** Always keep at least this many recent backups regardless of age. */\n keepMinimum?: number;\n /**\n * Schemas to exclude from the dump. Defaults to none, the same as\n * `rebase db backup` — see {@link createBackupCron} for why `rebase` in\n * particular must stay in.\n */\n excludeSchemas?: string[];\n /** Cron job display name. */\n name?: string;\n /** Whether the job is enabled. */\n enabled?: boolean;\n}\n\nexport interface EnvResolution {\n /** Resolved config, present when a schedule is configured. */\n config?: Omit<BackupCronConfig, \"storage\">;\n /** True when `BACKUP_SCHEDULE` is unset — the cron should be a no-op. */\n disabled?: boolean;\n /** Human-readable reason the config could not be built. */\n error?: string;\n}\n\n/**\n * Build backup-cron config purely from environment variables.\n * Recognised keys:\n * - `BACKUP_SCHEDULE` cron expression (required to enable)\n * - `BACKUP_DESTINATION` local path or `s3://` / `gs://` URL (required)\n * - `BACKUP_RETENTION_DAYS` integer days (optional)\n * - `BACKUP_KEEP_MINIMUM` integer count (optional)\n * - `DATABASE_URL` connection string\n *\n * Pure and side-effect free so it can be unit-tested without a database.\n */\nexport function backupCronConfigFromEnv(env: Record<string, string | undefined>): EnvResolution {\n const schedule = env.BACKUP_SCHEDULE?.trim();\n if (!schedule) {\n return { disabled: true };\n }\n\n const connectionString = resolveConnectionString(env);\n if (!connectionString) {\n return { error: \"BACKUP_SCHEDULE is set but DATABASE_URL is not configured.\" };\n }\n\n const destinationRaw = env.BACKUP_DESTINATION?.trim();\n if (!destinationRaw) {\n return { error: \"BACKUP_SCHEDULE is set but BACKUP_DESTINATION is not configured.\" };\n }\n const destination = parseBackupDestination(destinationRaw);\n\n const retentionDays = parseOptionalInt(env.BACKUP_RETENTION_DAYS);\n if (retentionDays === \"invalid\") {\n return { error: `BACKUP_RETENTION_DAYS must be an integer, got \"${env.BACKUP_RETENTION_DAYS}\".` };\n }\n const keepMinimum = parseOptionalInt(env.BACKUP_KEEP_MINIMUM);\n if (keepMinimum === \"invalid\") {\n return { error: `BACKUP_KEEP_MINIMUM must be an integer, got \"${env.BACKUP_KEEP_MINIMUM}\".` };\n }\n\n return {\n config: {\n schedule,\n connectionString,\n destination,\n retentionDays: retentionDays ?? undefined,\n keepMinimum: keepMinimum ?? undefined\n }\n };\n}\n\nfunction parseOptionalInt(value: string | undefined): number | null | \"invalid\" {\n if (value === undefined || value.trim() === \"\") return null;\n const n = Number(value);\n if (!Number.isInteger(n) || n < 0) return \"invalid\";\n return n;\n}\n\n/**\n * Create a {@link CronJobDefinition} that dumps the database, uploads the\n * result to the configured destination, and prunes old backups. Object\n * destinations require {@link BackupCronConfig.storage}.\n *\n * The dump excludes nothing unless `excludeSchemas` says otherwise, and that\n * default is not this function's to set: the option goes to `createDump`\n * untouched, so `buildPgDumpArgs` decides for the cron and `rebase db backup`\n * alike. The cron used to carry its own default of `[\"rebase\"]`, meant to\n * leave out Atlas's revision table, and it left out a lot more. `rebase` is\n * where the framework keeps\n * everything that is not a collection: the auth tables (`users`, identities,\n * refresh tokens, MFA factors, recovery codes, `app_config`, `schema_meta`),\n * API keys, record history, the job queue, cron logs, channel history,\n * branches and idempotency keys. It also holds the functions that every\n * generated RLS policy and CDC trigger calls (`rebase.uid()`,\n * `rebase.roles()`, `rebase.rebase_cdc_notify()`).\n *\n * None of that can be left for boot to rebuild. Boot recreates the tables\n * and functions, but never the rows in them. And a dump that has a policy\n * or trigger on a `public` table without the function it calls cannot be\n * restored into an empty database: `pg_restore --exit-on-error` stops at\n * the first such statement, and every collection's generated policies call\n * `rebase.uid()`. So every scheduled backup lost every user account, and\n * could not be restored anyway, while `rebase db backup` took a complete\n * one. The revision table belongs in a backup too: without it, a restored\n * database has tables but no record of the migrations that made them.\n */\nexport function createBackupCron(config: BackupCronConfig): CronJobDefinition {\n const dbName = parseDbNameFromUrl(config.connectionString) ?? \"database\";\n if (config.enabled ?? true) warnOfMissingTools(config.name ?? \"Scheduled database backup\");\n\n const job: CronJobDefinition = {\n name: config.name ?? \"Scheduled database backup\",\n schedule: config.schedule,\n description: \"Dumps the Postgres database and uploads it to the configured backup destination.\",\n enabled: config.enabled ?? true,\n // Backups of a large database can take a while; allow up to an hour.\n timeoutSeconds: 3600,\n async handler({ log }) {\n const { createDump, discardPartialDump, pruneBackups, uploadBackup, validateDump } =\n await import(\"./backup-service\");\n const { destination } = config;\n\n if (destination.kind !== \"local\" && !config.storage) {\n throw new Error(\n `Backup destination is ${destination.kind} but no storage controller was provided. ` +\n \"Pass the backend's configured StorageController to createBackupCron({ storage }).\"\n );\n }\n\n log(`Starting backup of \"${dbName}\"…`);\n const outDir = destination.kind === \"local\" ? destination.path : undefined;\n const dump = await createDump({\n connectionString: config.connectionString,\n dbName,\n outDir,\n excludeSchemas: config.excludeSchemas\n });\n log(`Dump created: ${dump.fileName} (${formatBytes(dump.sizeBytes)})`);\n\n // Validate BEFORE pruning: a corrupt-but-exit-0 dump must never be\n // the reason the last good backup gets deleted.\n const check = await validateDump(dump.localFile);\n if (!check.ok) {\n // Discarded for every destination, the roles sidecar with it.\n // On a local one the file *is* the backup: left behind, it\n // keeps a valid backup's name, and retention — which ranks by\n // timestamp and never opens a file — counts it among the\n // newest it keeps while pruning a real backup beneath it.\n if (dump.globalsFile) discardPartialDump(dump.globalsFile);\n discardPartialDump(dump.localFile);\n throw new Error(`New backup failed validation — skipping upload and pruning to protect existing backups. ${check.reason}`);\n }\n\n let storedKey = dump.localFile;\n try {\n if (destination.kind !== \"local\") {\n const uploaded = await uploadBackup(config.storage!, dump.localFile, destination);\n storedKey = uploaded.storageUrl;\n log(`Uploaded to ${uploaded.storageUrl}`);\n // Upload the roles sidecar so a restore can recreate the\n // roles the dump's GRANT/RLS statements depend on.\n if (dump.globalsFile && fs.existsSync(dump.globalsFile)) {\n const g = await uploadBackup(config.storage!, dump.globalsFile, destination);\n log(`Uploaded roles sidecar to ${g.storageUrl}`);\n }\n }\n } finally {\n // For object-storage destinations the local dump was a temp\n // file — remove it once uploaded (or on failure).\n if (destination.kind !== \"local\" && fs.existsSync(dump.localFile)) {\n fs.unlinkSync(dump.localFile);\n }\n if (dump.globalsFile && destination.kind !== \"local\" && fs.existsSync(dump.globalsFile)) {\n fs.unlinkSync(dump.globalsFile);\n }\n }\n\n let pruned: string[] = [];\n if (config.retentionDays && config.retentionDays > 0) {\n pruned = await pruneBackups(\n destination,\n { retentionDays: config.retentionDays, keepMinimum: config.keepMinimum },\n config.storage\n );\n if (pruned.length > 0) {\n log(`Pruned ${pruned.length} backup(s) older than ${config.retentionDays} day(s).`);\n }\n }\n\n return {\n backup: storedKey,\n sizeBytes: dump.sizeBytes,\n pruned: pruned.length\n };\n }\n };\n\n // How the Backups panel finds this job among the project's crons: its id is\n // the cron file's name and its display name is configurable. A registry\n // symbol, so `@rebasepro/server` reads it without importing this package\n // (it must not), and non-enumerable, so a spread or a JSON dump of the\n // definition is unchanged. See `readBackupSchedule` in the server package.\n Object.defineProperty(job, Symbol.for(\"rebase.backupCron\"), { value: true });\n return job;\n}\n\n/**\n * Say at registration, not at 03:00, that the job cannot run.\n *\n * Each run needs `pg_dump` for the dump and `pg_dumpall` for the roles\n * sidecar. Without them every run fails with \"Could not find the 'pg_dump'\n * binary\", and that was the first an operator heard of it — a runtime image\n * once shipped without the client tools, and every nightly backup failed.\n */\nfunction warnOfMissingTools(jobName: string): void {\n const needed: PgTool[] = [\"pg_dump\", \"pg_dumpall\"];\n const missing = needed.filter(tool => !resolvePgBinary(tool));\n if (missing.length === 0) return;\n logger.error(\n `\"${jobName}\" is scheduled, but ${missing.map(tool => `'${tool}'`).join(\" and \")} ` +\n `${missing.length > 1 ? \"were\" : \"was\"} not found on this machine, so every run will fail. ` +\n \"Install the PostgreSQL client tools (the same major version as the server, or newer), \" +\n `or set ${missing.map(pgToolVariable).join(\" / \")} to the binary.`\n );\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n","import { DatabaseAdapter, InitializedDriver, RealtimeProvider, DataDriver, DatabaseAdmin, BootstrappedAuth } from \"@rebasepro/types\";\nimport { createPostgresBootstrapper } from \"./PostgresBootstrapper\";\nimport type { PostgresDriverConfig } from \"./PostgresBootstrapper\";\n\n/**\n * Creates a Postgres database adapter for Rebase.\n */\nexport function createPostgresAdapter(pgConfig: PostgresDriverConfig): DatabaseAdapter {\n const bootstrapper = createPostgresBootstrapper(pgConfig);\n\n return {\n type: bootstrapper.type,\n\n async initializeDriver(config) {\n return bootstrapper.initializeDriver(config);\n },\n\n async initializeRealtime(driverResult) {\n if (bootstrapper.initializeRealtime) {\n return bootstrapper.initializeRealtime({}, driverResult);\n }\n return undefined;\n },\n\n async initializeAuth(config, driverResult) {\n if (bootstrapper.initializeAuth) {\n return bootstrapper.initializeAuth(config, driverResult);\n }\n return undefined;\n },\n\n async initializeHistory(config, driverResult) {\n if (bootstrapper.initializeHistory) {\n return bootstrapper.initializeHistory(config, driverResult);\n }\n return undefined;\n },\n\n // `adapter` and `limits` are forwarded for the same reason the schema\n // hooks below are: dropping an argument here is invisible at the call\n // site and silent at runtime. `adapter` decided whether the realtime\n // socket authenticates at all — without it, a server whose auth comes\n // from an AuthAdapter fell back to \"is a jwtSecret set?\", answered no,\n // and admitted every client. `limits` are the data API's body and rate\n // limits; dropped, the socket has neither.\n initializeWebsockets(server, realtimeService, driver, config, adapter, limits) {\n if (bootstrapper.initializeWebsockets) {\n return bootstrapper.initializeWebsockets(server, realtimeService, driver, config, adapter, limits);\n }\n },\n\n // Forwarded so the boot-time schema/RLS provisioning is reachable when\n // this adapter is wrapped back into a bootstrapper. Omitting either left\n // a managed tenant with no tables (500 on every data route) or tables\n // but no policies (401 on every read) — the create step never ran.\n verifyConnection: bootstrapper.verifyConnection\n ? (driverResult) => bootstrapper.verifyConnection!(driverResult)\n : undefined,\n\n ensureCollectionSchema: bootstrapper.ensureCollectionSchema\n ? (collections, driverResult, log) =>\n bootstrapper.ensureCollectionSchema!(collections, driverResult, log)\n : undefined,\n\n // Same rule, and it bit at once: the method existed on the bootstrapper,\n // this wrapper did not forward it, and a second database booted with\n // tables and no helper functions — every policy on it silently failed\n // to create. The adapter type is where a forgotten forward should fail\n // to compile; `adapter-forwarding.test.ts` sweeps for exactly this.\n ensureRlsRuntime: bootstrapper.ensureRlsRuntime\n ? (driverResult) => bootstrapper.ensureRlsRuntime!(driverResult)\n : undefined,\n\n ensureCollectionPolicies: bootstrapper.ensureCollectionPolicies\n ? (collections, driverResult, log) =>\n bootstrapper.ensureCollectionPolicies!(collections, driverResult, log)\n : undefined,\n\n finalizeSecurityPosture: bootstrapper.finalizeSecurityPosture\n ? (driverResult) => bootstrapper.finalizeSecurityPosture!(driverResult)\n : undefined,\n\n // Same forwarding rule, third instance. Dropping these is not a type\n // error and not a runtime error: the stamp is simply never written and\n // never read, so a split deployment loses the only thing that would tell\n // it a unit is serving against a schema it was not built for — and a\n // check that is off looks exactly like a check that passed. This one was\n // in fact dropped on the first attempt, and the e2e caught it.\n readCollectionsSchemaVersion: bootstrapper.readCollectionsSchemaVersion\n ? (driverResult) => bootstrapper.readCollectionsSchemaVersion!(driverResult)\n : undefined,\n\n stampCollectionsSchemaVersion: bootstrapper.stampCollectionsSchemaVersion\n ? (version, driverResult) => bootstrapper.stampCollectionsSchemaVersion!(version, driverResult)\n : undefined,\n\n getAdmin(driverResult) {\n if (bootstrapper.getAdmin) {\n return bootstrapper.getAdmin(driverResult);\n }\n return undefined;\n }\n };\n}\n","/**\n * Every `rebase db …` line this driver answers, judged without connecting to\n * anything.\n *\n * The driver refuses a flag a command does not take, but it is spawned only\n * once the CLI has a database for it, and getting one means starting one: the\n * remembered compose container, or the managed PGlite daemon. So `rebase db\n * push --dry-rn` booted the database and then said the flag did not exist.\n * The CLI runs this first, from the driver's own code, so the answer it gives\n * before starting anything and the one the driver would give after are one\n * check, not two lists to keep in step.\n *\n * Each entry calls the parse its command runs itself, never a copy of it.\n */\nimport { assertKnownFlags } from \"./cli-flags\";\nimport { assertKnownBranchFlags } from \"./branch-argv\";\nimport { backupActionOf } from \"./backup-argv\";\nimport { parseBackupLine, parseBackupsListLine, parseRestoreLine } from \"./backup/backup-cli\";\nimport { isDriverDbSubcommand, type DriverDbSubcommand } from \"./db-subcommands\";\n\n/**\n * The check each subcommand's line gets. A `Record` over the union, so a\n * subcommand added to `DRIVER_DB_SUBCOMMANDS` does not compile until it says\n * how its line is judged.\n */\nconst LINE_CHECKS: Record<DriverDbSubcommand, (args: string[]) => void> = {\n push: args => assertKnownFlags(\"db\", \"push\", args),\n generate: args => assertKnownFlags(\"db\", \"generate\", args),\n migrate: args => assertKnownFlags(\"db\", \"migrate\", args),\n // The action's own table; an action the table does not list is the\n // dispatch's to answer, in its own words.\n branch: (args) => {\n if (args[2] !== undefined) assertKnownBranchFlags(args[2], args.slice(3));\n },\n backup: (args) => {\n if (backupActionOf(args) === \"list\") parseBackupsListLine(args);\n else parseBackupLine(args);\n },\n restore: (args) => { parseRestoreLine(args); },\n backups: (args) => {\n if (args[2] === \"list\") parseBackupsListLine(args);\n }\n};\n\n/**\n * Throw what the driver would say of a `rebase db …` line it refuses on its\n * flags, before it would connect: an option the command does not take, and\n * the checks `db backup` and `db restore` make of their own line (a stray\n * word, a pattern that leaves out the rebase schema).\n *\n * Reads no environment and opens nothing, so it is safe to run before a\n * database exists.\n *\n * @param args the driver's line: `[\"db\", \"<subcommand>\", …]`, without the\n * CLI's own words in front. A line for a subcommand this driver does not\n * answer passes, for the dispatch to refuse.\n */\nexport function assertDbCommandLine(args: readonly string[]): void {\n const [domain, subcommand] = args;\n if (domain !== \"db\" || !isDriverDbSubcommand(subcommand)) return;\n // `--help` is answered before any check, by the CLI and by the dispatch.\n if (args.includes(\"--help\") || args.includes(\"-h\")) return;\n LINE_CHECKS[subcommand]([...args]);\n}\n","/**\n * Do the migration files still describe the collections?\n *\n * `rebase db generate` asks Atlas, which needs a dev database to replay the\n * migrations into — seconds of Docker, or the managed PGlite, before it can say\n * a word. `rebase verify` runs after every edit an agent makes to a collection\n * file and in CI, where neither is acceptable, so this answers the narrower\n * question offline: replay the `CREATE TABLE` / `ALTER TABLE` / `DROP TABLE`\n * statements the migrations contain, and compare the tables and columns that\n * leaves with the ones the schema plan derives from the collections.\n *\n * Deliberately tables and columns only. A changed type or default is a real\n * drift too, but reading it back out of Atlas's SQL means parsing every type\n * spelling Postgres accepts, and a check that misreads `character varying(255)`\n * as a change cries wolf on every run. A missing table or column is the drift\n * that breaks a deploy — the runtime queries a column the migrated database\n * does not have — and it is unambiguous in the text.\n *\n * Policies are not compared: the runtime applies a collection's policies at\n * boot whether or not a migration carries them.\n */\nimport type { CollectionConfig } from \"@rebasepro/types\";\nimport { planSchema } from \"./plan/plan-schema\";\n\n/** One difference between the collections and what the migrations build. */\nexport interface UnmigratedChange {\n /**\n * - `missing-table` / `missing-column` — the collections declare it and no\n * migration creates it.\n * - `removed-column` — a migration creates it, the collection no longer\n * declares it, and no later migration drops it.\n */\n kind: \"missing-table\" | \"missing-column\" | \"removed-column\";\n /** `schema.table`. */\n table: string;\n column?: string;\n /** The collection that declares the table, when one does (a junction has none). */\n slug?: string;\n}\n\n/** A table as the migrations leave it. */\ninterface MigratedTable {\n /** Every column, however it was added. */\n columns: Set<string>;\n /**\n * The columns Atlas wrote: from a `CREATE TABLE` or an `ADD COLUMN` without\n * `IF NOT EXISTS`. The search, vector and trigger DDL `db generate` appends\n * after Atlas's half always says `IF NOT EXISTS`, and the planner keeps\n * those columns out of `TablePlan.columns` — so only these are candidates\n * for \"removed from the collection\".\n */\n managed: Set<string>;\n}\n\n/** Blank out `--` and `/* *\\/` comments, leaving string and identifier literals alone. */\nfunction stripComments(sql: string): string {\n let out = \"\";\n let i = 0;\n while (i < sql.length) {\n const ch = sql[i];\n if (ch === \"'\" || ch === \"\\\"\") {\n const end = sql.indexOf(ch, i + 1);\n const stop = end === -1 ? sql.length : end + 1;\n out += sql.slice(i, stop);\n i = stop;\n } else if (ch === \"-\" && sql[i + 1] === \"-\") {\n const end = sql.indexOf(\"\\n\", i);\n i = end === -1 ? sql.length : end;\n } else if (ch === \"/\" && sql[i + 1] === \"*\") {\n const end = sql.indexOf(\"*/\", i + 2);\n i = end === -1 ? sql.length : end + 2;\n } else if (ch === \"$\") {\n // A dollar-quoted body (`$$ … $$`, `$fn$ … $fn$`) is copied whole: a\n // function body is full of `--` and `;` that are not ours to read.\n const tag = /^\\$[A-Za-z_]*\\$/.exec(sql.slice(i))?.[0];\n if (!tag) {\n out += ch;\n i++;\n continue;\n }\n const end = sql.indexOf(tag, i + tag.length);\n const stop = end === -1 ? sql.length : end + tag.length;\n out += sql.slice(i, stop);\n i = stop;\n } else {\n out += ch;\n i++;\n }\n }\n return out;\n}\n\n/**\n * Split on a separator at nesting depth zero, outside quotes and dollar quotes.\n * `;` splits a file into statements, `,` a column list into its elements.\n */\nfunction splitTopLevel(text: string, separator: \";\" | \",\"): string[] {\n const parts: string[] = [];\n let depth = 0;\n let start = 0;\n let i = 0;\n while (i < text.length) {\n const ch = text[i];\n if (ch === \"'\" || ch === \"\\\"\") {\n const end = text.indexOf(ch, i + 1);\n i = end === -1 ? text.length : end + 1;\n continue;\n }\n if (ch === \"$\") {\n const tag = /^\\$[A-Za-z_]*\\$/.exec(text.slice(i))?.[0];\n if (tag) {\n const end = text.indexOf(tag, i + tag.length);\n i = end === -1 ? text.length : end + tag.length;\n continue;\n }\n }\n if (ch === \"(\") depth++;\n else if (ch === \")\") depth--;\n else if (ch === separator && depth === 0) {\n parts.push(text.slice(start, i));\n start = i + 1;\n }\n i++;\n }\n parts.push(text.slice(start));\n return parts.map(p => p.trim()).filter(p => p.length > 0);\n}\n\n/** One identifier: `\"Quoted\"` keeps its case, a bare word folds to lower case as Postgres folds it. */\nconst IDENT = String.raw`(?:\"(?:[^\"]|\"\")+\"|[A-Za-z_][A-Za-z0-9_$]*)`;\n/** An optionally schema-qualified name. */\nconst QUALIFIED = String.raw`${IDENT}(?:\\s*\\.\\s*${IDENT})?`;\n\nfunction unquote(identifier: string): string {\n const trimmed = identifier.trim();\n return trimmed.startsWith(\"\\\"\") ? trimmed.slice(1, -1).replace(/\"\"/g, \"\\\"\") : trimmed.toLowerCase();\n}\n\n/** `\"public\".\"posts\"` → `public.posts`; an unqualified name is in `public`. */\nfunction qualifiedName(name: string): string {\n const [first = \"\", second] = name.match(new RegExp(IDENT, \"g\")) ?? [];\n return second !== undefined ? `${unquote(first)}.${unquote(second)}` : `public.${unquote(first)}`;\n}\n\n/** Words that open a table constraint rather than a column, inside `CREATE TABLE ( … )`. */\nconst TABLE_CONSTRAINT = /^(CONSTRAINT|PRIMARY|UNIQUE|FOREIGN|CHECK|EXCLUDE|LIKE)\\b/i;\n\nconst CREATE_TABLE = new RegExp(String.raw`^CREATE\\s+(?:UNLOGGED\\s+)?TABLE\\s+(IF\\s+NOT\\s+EXISTS\\s+)?(${QUALIFIED})\\s*\\(([\\s\\S]*)\\)[^)]*$`, \"i\");\nconst ALTER_TABLE = new RegExp(String.raw`^ALTER\\s+TABLE\\s+(?:IF\\s+EXISTS\\s+)?(?:ONLY\\s+)?(${QUALIFIED})\\s+([\\s\\S]*)$`, \"i\");\nconst DROP_TABLE = /^DROP\\s+TABLE\\s+(?:IF\\s+EXISTS\\s+)?([\\s\\S]*?)(?:\\s+(?:CASCADE|RESTRICT))?$/i;\nconst ADD_COLUMN = new RegExp(String.raw`^ADD\\s+(?:COLUMN\\s+)?(IF\\s+NOT\\s+EXISTS\\s+)?(${IDENT})`, \"i\");\nconst DROP_COLUMN = new RegExp(String.raw`^DROP\\s+(?:COLUMN\\s+)?(?:IF\\s+EXISTS\\s+)?(${IDENT})`, \"i\");\nconst RENAME_COLUMN = new RegExp(String.raw`^RENAME\\s+(?:COLUMN\\s+)?(${IDENT})\\s+TO\\s+(${IDENT})$`, \"i\");\nconst RENAME_TABLE = new RegExp(String.raw`^RENAME\\s+TO\\s+(${IDENT})$`, \"i\");\n\n/**\n * The tables and columns the migrations leave behind, replayed in order.\n *\n * Exported for its tests.\n */\nexport function replayMigrationTables(migrations: readonly string[]): Map<string, MigratedTable> {\n const tables = new Map<string, MigratedTable>();\n const tableOf = (name: string): MigratedTable => {\n let table = tables.get(name);\n if (!table) {\n table = { columns: new Set(), managed: new Set() };\n tables.set(name, table);\n }\n return table;\n };\n\n for (const migration of migrations) {\n for (const raw of splitTopLevel(stripComments(migration), \";\")) {\n const statement = raw.replace(/\\s+/g, \" \").trim();\n\n const created = CREATE_TABLE.exec(statement);\n if (created) {\n const table = tableOf(qualifiedName(created[2]));\n const managed = !created[1];\n for (const element of splitTopLevel(created[3], \",\")) {\n if (TABLE_CONSTRAINT.test(element)) continue;\n const column = new RegExp(`^${IDENT}`).exec(element)?.[0];\n if (!column) continue;\n table.columns.add(unquote(column));\n if (managed) table.managed.add(unquote(column));\n }\n continue;\n }\n\n const altered = ALTER_TABLE.exec(statement);\n if (altered) {\n const name = qualifiedName(altered[1]);\n for (const action of splitTopLevel(altered[2], \",\")) {\n const renamedTable = RENAME_TABLE.exec(action);\n if (renamedTable) {\n const existing = tables.get(name);\n tables.delete(name);\n const target = `${name.split(\".\")[0]}.${unquote(renamedTable[1])}`;\n if (existing) tables.set(target, existing);\n continue;\n }\n const renamed = RENAME_COLUMN.exec(action);\n if (renamed) {\n const table = tableOf(name);\n const from = unquote(renamed[1]);\n const to = unquote(renamed[2]);\n table.columns.delete(from);\n table.columns.add(to);\n if (table.managed.delete(from)) table.managed.add(to);\n continue;\n }\n const added = ADD_COLUMN.exec(action);\n if (added && !/^ADD\\s+(CONSTRAINT|PRIMARY|UNIQUE|FOREIGN|CHECK|EXCLUDE)\\b/i.test(action)) {\n const table = tableOf(name);\n const column = unquote(added[2]);\n table.columns.add(column);\n if (!added[1]) table.managed.add(column);\n continue;\n }\n const dropped = DROP_COLUMN.exec(action);\n if (dropped && !/^DROP\\s+(CONSTRAINT|DEFAULT|NOT|EXPRESSION|IDENTITY)\\b/i.test(action)) {\n const table = tableOf(name);\n const column = unquote(dropped[1]);\n table.columns.delete(column);\n table.managed.delete(column);\n }\n }\n continue;\n }\n\n const droppedTables = DROP_TABLE.exec(statement);\n if (droppedTables) {\n for (const name of splitTopLevel(droppedTables[1], \",\")) tables.delete(qualifiedName(name));\n }\n }\n }\n return tables;\n}\n\n/**\n * What the collections declare that the migrations do not build, and what the\n * migrations still build that the collections dropped.\n *\n * `migrations` are the contents of every `.sql` file in the migrations\n * directory, in file-name order — Atlas's order. An empty list is a project\n * with no migrations at all, for which everything is missing; whether that is a\n * problem (a project on `db push` has none by design) is the caller's call.\n *\n * Throws what {@link planSchema} throws for a configuration it cannot plan.\n */\nexport function findUnmigratedSchema(\n collections: CollectionConfig[],\n migrations: readonly string[]\n): UnmigratedChange[] {\n const plan = planSchema(collections);\n const migrated = replayMigrationTables(migrations);\n const changes: UnmigratedChange[] = [];\n\n for (const table of plan.tables) {\n const slug = table.slug;\n const existing = migrated.get(table.qualified);\n if (!existing) {\n changes.push({ kind: \"missing-table\", table: table.qualified, ...(slug && { slug }) });\n continue;\n }\n // The users table is shared with the auth tables boot provisions\n // (`auth-users-columns`), which add columns no collection declares and\n // no migration has to carry. The table's existence is the fact here.\n if (table.auth) continue;\n\n // A search column is carved out of Atlas's diff and added by the\n // search DDL `db generate` appends (`ADD COLUMN IF NOT EXISTS`), which\n // a migration from before the block was declared does not carry and\n // the runtime applies itself — not a migration's to own.\n const planned = new Set(table.columns\n .filter(column => column.source.kind !== \"search\")\n .map(column => column.column));\n for (const column of planned) {\n if (!existing.columns.has(column)) {\n changes.push({ kind: \"missing-column\", table: table.qualified, column, ...(slug && { slug }) });\n }\n }\n for (const column of existing.managed) {\n if (!planned.has(column)) {\n changes.push({ kind: \"removed-column\", table: table.qualified, column, ...(slug && { slug }) });\n }\n }\n }\n return changes;\n}\n"],"mappings":"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"}
|