@rebasepro/server-postgres 0.24.0 → 0.24.1-canary.gcb918fc
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/README.md +16 -0
- package/dist/{BranchService-BRt78gfa.js → BranchService-Bsn2wfaj.js} +3 -2
- package/dist/{BranchService-BRt78gfa.js.map → BranchService-Bsn2wfaj.js.map} +1 -1
- package/dist/PostgresBackendDriver.d.ts +91 -2
- package/dist/PostgresBootstrapper.d.ts +101 -2
- package/dist/auth/ensure-tables.d.ts +12 -0
- package/dist/auth/services.d.ts +6 -0
- package/dist/backup/backup-cli.d.ts +14 -0
- package/dist/backup/backup-service.d.ts +2 -2
- package/dist/backup/pg-binary.d.ts +5 -0
- package/dist/{backup-cli-DW5p9_zv.js → backup-cli-DnpJi6I7.js} +37 -29
- package/dist/backup-cli-DnpJi6I7.js.map +1 -0
- package/dist/{backup-service-EwcDVG-8.js → backup-service-BTBB7RXn.js} +55 -21
- package/dist/backup-service-BTBB7RXn.js.map +1 -0
- package/dist/boot-ddl-DVpsFN6r.js +105 -0
- package/dist/boot-ddl-DVpsFN6r.js.map +1 -0
- package/dist/cli-console-Ca6ZlAVU.js +154 -0
- package/dist/cli-console-Ca6ZlAVU.js.map +1 -0
- package/dist/cli-console.d.ts +39 -0
- package/dist/cli-errors-CaI4ntyD.js +1919 -0
- package/dist/cli-errors-CaI4ntyD.js.map +1 -0
- package/dist/cli-errors.d.ts +51 -5
- package/dist/{cli-flags-BglvpjHv.js → cli-flags-JNI-pwSf.js} +6 -3
- package/dist/cli-flags-JNI-pwSf.js.map +1 -0
- package/dist/cli-helpers.d.ts +40 -0
- package/dist/cli-output.d.ts +16 -28
- package/dist/cli-push.d.ts +87 -0
- package/dist/cli.js +566 -182
- package/dist/cli.js.map +1 -1
- package/dist/{column-plan-helpers-1-LQD0yI.js → column-plan-helpers-drZIsSNf.js} +10 -4
- package/dist/{column-plan-helpers-1-LQD0yI.js.map → column-plan-helpers-drZIsSNf.js.map} +1 -1
- package/dist/{databasePoolManager-Bj5FbeAs.js → connection-C9mT8bgf.js} +24 -112
- package/dist/connection-C9mT8bgf.js.map +1 -0
- package/dist/connection.d.ts +13 -2
- package/dist/databasePoolManager-BIuewidb.js +119 -0
- package/dist/databasePoolManager-BIuewidb.js.map +1 -0
- package/dist/{ensure-collection-tables-kkkHk8oo.js → deferred-index-build-DP1_SNFo.js} +484 -346
- package/dist/deferred-index-build-DP1_SNFo.js.map +1 -0
- package/dist/{doctor-C-sYWbmt.js → doctor-C4WiftHm.js} +63 -52
- package/dist/doctor-C4WiftHm.js.map +1 -0
- package/dist/ensure-collection-policies-CXpbeNag.js +541 -0
- package/dist/ensure-collection-policies-CXpbeNag.js.map +1 -0
- package/dist/ensure-collection-tables-BhsU6qUE.js +532 -0
- package/dist/ensure-collection-tables-BhsU6qUE.js.map +1 -0
- package/dist/{ensure-tables-BmI_tRxc.js → ensure-tables-CVm_yvzb.js} +173 -77
- package/dist/ensure-tables-CVm_yvzb.js.map +1 -0
- package/dist/enum-labels-kept-D8fI1MoX.js +56 -0
- package/dist/enum-labels-kept-D8fI1MoX.js.map +1 -0
- package/dist/generate-drizzle-schema-Bz52jH2h.js +57 -0
- package/dist/generate-drizzle-schema-Bz52jH2h.js.map +1 -0
- package/dist/{generate-drizzle-schema-logic-sSFDp6LR.js → generate-drizzle-schema-logic-B74WN37S.js} +5 -5
- package/dist/generate-drizzle-schema-logic-B74WN37S.js.map +1 -0
- package/dist/generate-files-CdEckX6c.js +199 -0
- package/dist/generate-files-CdEckX6c.js.map +1 -0
- package/dist/{generate-postgres-ddl-logic-BJsLaVNX.js → generate-postgres-ddl-logic-B64O2oZF.js} +3 -3
- package/dist/{generate-postgres-ddl-logic-BJsLaVNX.js.map → generate-postgres-ddl-logic-B64O2oZF.js.map} +1 -1
- package/dist/generated-sql.d.ts +8 -2
- package/dist/history/HistoryService.d.ts +64 -5
- package/dist/history/ensure-history-table.d.ts +63 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +139 -19236
- package/dist/index.es.js.map +1 -1
- package/dist/{introspect-db-logic-kCETE8TY.js → introspect-db-logic-C-Di8dA9.js} +44 -6
- package/dist/introspect-db-logic-C-Di8dA9.js.map +1 -0
- package/dist/{introspect-db-queries-C_Q5VgQw.js → introspect-db-queries-D6kAqReI.js} +23 -1
- package/dist/introspect-db-queries-D6kAqReI.js.map +1 -0
- package/dist/open-rebase-CbOur0TU.js +20858 -0
- package/dist/open-rebase-CbOur0TU.js.map +1 -0
- package/dist/open-rebase.d.ts +86 -0
- package/dist/pg-error-utils-vJezlXdI.js +388 -0
- package/dist/pg-error-utils-vJezlXdI.js.map +1 -0
- package/dist/{plan-schema-DU9exq6C.js → plan-schema-B1YF_tYq.js} +151 -41
- package/dist/plan-schema-B1YF_tYq.js.map +1 -0
- package/dist/{policy-drift-B-J2hhm0.js → policy-drift-Dm8i-s1J.js} +23 -6
- package/dist/policy-drift-Dm8i-s1J.js.map +1 -0
- package/dist/push-index-prebuild-Db4AQF4i.js +45 -0
- package/dist/push-index-prebuild-Db4AQF4i.js.map +1 -0
- package/dist/push-legacy-renames-BK6ilQq2.js +34 -0
- package/dist/push-legacy-renames-BK6ilQq2.js.map +1 -0
- package/dist/{render-ddl-Ds2t_d9V.js → render-ddl-CtC3y9Ol.js} +8 -4
- package/dist/render-ddl-CtC3y9Ol.js.map +1 -0
- package/dist/{rls-bootstrap-sql-_KNnjanK.js → rls-bootstrap-sql-RQUljnJ1.js} +125 -1270
- package/dist/rls-bootstrap-sql-RQUljnJ1.js.map +1 -0
- package/dist/{rls-enforcement-CfXOJJaW.js → rls-enforcement-DvzqlaL1.js} +129 -17
- package/dist/rls-enforcement-DvzqlaL1.js.map +1 -0
- package/dist/schema/auth-schema.d.ts +2 -2
- package/dist/schema/boot-ddl.d.ts +47 -0
- package/dist/schema/collection-index.d.ts +45 -0
- package/dist/schema/column-plan-helpers.d.ts +5 -0
- package/dist/schema/deferred-index-build.d.ts +131 -0
- package/dist/schema/destructive-sql.d.ts +7 -0
- package/dist/schema/doctor-cli.js +110 -6
- package/dist/schema/doctor-cli.js.map +1 -1
- package/dist/schema/doctor-search-scale.d.ts +45 -0
- package/dist/schema/doctor.d.ts +20 -1
- package/dist/schema/ensure-collection-policies.d.ts +73 -35
- package/dist/schema/ensure-collection-tables.d.ts +56 -3
- package/dist/schema/enum-labels-kept.d.ts +26 -0
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-files.d.ts +44 -0
- package/dist/schema/generate-next-step.d.ts +0 -13
- package/dist/schema/generate-postgres-ddl.js +10 -81
- package/dist/schema/generate-postgres-ddl.js.map +1 -1
- package/dist/schema/introspect-db-logic.d.ts +12 -0
- package/dist/schema/introspect-db-queries.d.ts +11 -1
- package/dist/schema/introspect-db-structure.d.ts +10 -0
- package/dist/schema/introspect-db.js +4 -5
- package/dist/schema/introspect-db.js.map +1 -1
- package/dist/schema/migration-post-process.d.ts +34 -0
- package/dist/schema/plan/diff-plan.d.ts +126 -0
- package/dist/schema/plan/types.d.ts +8 -0
- package/dist/schema/push-index-prebuild.d.ts +25 -0
- package/dist/schema/push-legacy-renames.d.ts +20 -0
- package/dist/schema/rls-bootstrap-sql.d.ts +15 -0
- package/dist/security/rls-enforcement.d.ts +32 -4
- package/dist/services/FetchService.d.ts +58 -8
- package/dist/services/PersistService.d.ts +15 -0
- package/dist/services/RelationService.d.ts +88 -3
- package/dist/services/cdc/CdcListener.d.ts +17 -6
- package/dist/services/cdc/trigger-cdc.d.ts +151 -27
- package/dist/services/channel-bus/index.d.ts +1 -1
- package/dist/services/channel-history.d.ts +15 -4
- package/dist/services/collection-helpers.d.ts +15 -0
- package/dist/services/field-viewer.d.ts +15 -1
- package/dist/services/pg-notify-listener.d.ts +7 -0
- package/dist/services/realtimeService.d.ts +314 -14
- package/dist/services/row-pipeline.d.ts +16 -9
- package/dist/services/socket-liveness.d.ts +42 -2
- package/dist/services/sql-script.d.ts +207 -3
- package/dist/services/vector-iterative-scan.d.ts +40 -0
- package/dist/services/write-depth.d.ts +6 -0
- package/dist/services/write-transaction-scope.d.ts +9 -0
- package/dist/socket-opt-ins.d.ts +64 -0
- package/dist/source-DBDbBTL4.js +811 -0
- package/dist/source-DBDbBTL4.js.map +1 -0
- package/dist/tsx-loader.d.ts +14 -0
- package/dist/utils/drizzle-conditions.d.ts +21 -1
- package/dist/utils/error-cause.d.ts +7 -0
- package/dist/utils/pg-error-utils.d.ts +2 -4
- package/dist/utils/result-rows.d.ts +10 -0
- package/dist/websocket-BLGc5yhs.js +1604 -0
- package/dist/websocket-BLGc5yhs.js.map +1 -0
- package/dist/websocket.d.ts +13 -0
- package/package.json +7 -7
- package/dist/backup-cli-DW5p9_zv.js.map +0 -1
- package/dist/backup-service-EwcDVG-8.js.map +0 -1
- package/dist/cli-errors-DsA-K9uP.js +0 -893
- package/dist/cli-errors-DsA-K9uP.js.map +0 -1
- package/dist/cli-flags-BglvpjHv.js.map +0 -1
- package/dist/cli-output-CNdMql-L.js +0 -47
- package/dist/cli-output-CNdMql-L.js.map +0 -1
- package/dist/collection-index-DdnsxiJ_.js +0 -286
- package/dist/collection-index-DdnsxiJ_.js.map +0 -1
- package/dist/databasePoolManager-Bj5FbeAs.js.map +0 -1
- package/dist/doctor-C-sYWbmt.js.map +0 -1
- package/dist/ensure-collection-policies-B1ureSIV.js +0 -160
- package/dist/ensure-collection-policies-B1ureSIV.js.map +0 -1
- package/dist/ensure-collection-tables-kkkHk8oo.js.map +0 -1
- package/dist/ensure-tables-BmI_tRxc.js.map +0 -1
- package/dist/generate-drizzle-schema-93M0lUxK.js +0 -123
- package/dist/generate-drizzle-schema-93M0lUxK.js.map +0 -1
- package/dist/generate-drizzle-schema-logic-sSFDp6LR.js.map +0 -1
- package/dist/introspect-db-logic-kCETE8TY.js.map +0 -1
- package/dist/introspect-db-queries-C_Q5VgQw.js.map +0 -1
- package/dist/plan-schema-DU9exq6C.js.map +0 -1
- package/dist/policy-drift-B-J2hhm0.js.map +0 -1
- package/dist/render-ddl-Ds2t_d9V.js.map +0 -1
- package/dist/rls-bootstrap-sql-_KNnjanK.js.map +0 -1
- package/dist/rls-enforcement-CfXOJJaW.js.map +0 -1
- package/dist/source-Br7L7GOI.js +0 -410
- package/dist/source-Br7L7GOI.js.map +0 -1
- package/dist/utils/sql-redaction.d.ts +0 -21
package/dist/cli.js
CHANGED
|
@@ -1,20 +1,41 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
3
|
import { a as __toESM } from "./__vite-browser-external-BnuHet1e.js";
|
|
4
|
-
import { a as formatForeignGeneratedColumnBanner, c as reportCommandFailure, i as diagnoseDbError, r as diagnoseAtlasFailure, t as checkDatabaseConnectivity } from "./cli-errors-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { t as chalk } from "./source-Br7L7GOI.js";
|
|
4
|
+
import { A as queryColumnTypes, B as prepareGeneratedSqlDir, C as getForeignIndexExcludes, D as getVectorExcludes, E as getTriggerExcludes, F as resolveLocalBin, G as isSyncedPlan, H as detectDestructiveStatements, I as withAppClient, L as DESIRED_STATE_URL, M as readSearchDdl, N as readTriggersDdl, O as loadCollectionsForCli, P as readVectorDdl, R as GENERATED_SQL_DIR, S as ensureDevDatabaseExists, T as getTableExcludes, U as extractPlanStatements, V as decidePushSafety, W as fromAlterTable, _ as describeReinstallCommand, a as formatForeignGeneratedColumnBanner, b as dropDevDatabase, c as reportCommandFailure, d as applyTriggersDdl, f as applyVectorDdl, g as describeDevAddCommand, h as describeBuildScriptRemedy, i as diagnoseDbError, j as queryGeneratedColumnDependencies, k as promptConfirm, l as ExcludeIntrospectionError, m as closeAppClients, p as atlasDevDatabaseUrl, r as diagnoseAtlasFailure, t as checkDatabaseConnectivity, u as applySearchDdl, v as detectProjectPackageManager, w as getSearchExcludes, x as dropGeneratedColumns, y as diagnoseMissingBin, z as generatedSqlDir } from "./cli-errors-CaI4ntyD.js";
|
|
5
|
+
import { a as outWarn, i as outError, r as out, t as chalk } from "./source-DBDbBTL4.js";
|
|
6
|
+
import { o as execa, r as dropLegacyAuthSchema, s as forLibpq, t as RLS_BOOTSTRAP_SQL } from "./rls-bootstrap-sql-RQUljnJ1.js";
|
|
8
7
|
import { t as require_arg } from "./arg-Dni7MzLB.js";
|
|
9
|
-
import {
|
|
10
|
-
import { logger } from "@rebasepro/server";
|
|
8
|
+
import { i as parseDriverLine, n as assertKnownFlags, r as collectionsPathIn } from "./cli-flags-JNI-pwSf.js";
|
|
11
9
|
import { formatRelativeTime } from "@rebasepro/utils";
|
|
12
|
-
import fs from "fs";
|
|
13
10
|
import path from "path";
|
|
14
|
-
import
|
|
15
|
-
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
13
|
+
import { createRequire } from "module";
|
|
14
|
+
//#region src/tsx-loader.ts
|
|
16
15
|
var import_arg = /* @__PURE__ */ __toESM(require_arg(), 1);
|
|
17
16
|
/**
|
|
17
|
+
* tsx's loader, as the URL `node --import` takes, or null when none resolves.
|
|
18
|
+
*
|
|
19
|
+
* For running one of this driver's scripts in one Node process. The `tsx`
|
|
20
|
+
* binary is a relay — pnpm's `.bin/tsx` shell shim, then tsx's CLI in one Node,
|
|
21
|
+
* which spawns a second Node with this same loader to run the script — so
|
|
22
|
+
* spawning through it costs a shell and a Node start (~50–100 ms) per child.
|
|
23
|
+
*
|
|
24
|
+
* Resolved the way Node resolves the package: from the project the driver
|
|
25
|
+
* runs in first (its cwd is the backend, where a scaffold declares tsx), then
|
|
26
|
+
* from this package. A URL rather than a path: a Windows path is not a valid
|
|
27
|
+
* `--import` specifier.
|
|
28
|
+
*/
|
|
29
|
+
function resolveTsxLoader() {
|
|
30
|
+
const bases = [path.join(process.cwd(), "noop.js"), import.meta.url];
|
|
31
|
+
for (const base of bases) try {
|
|
32
|
+
return pathToFileURL(createRequire(base).resolve("tsx")).href;
|
|
33
|
+
} catch {}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/schema/generated-column-conflicts.ts
|
|
38
|
+
/**
|
|
18
39
|
* Which columns an Atlas plan cannot touch, because a generated column reads
|
|
19
40
|
* them.
|
|
20
41
|
*
|
|
@@ -533,6 +554,60 @@ function tidy(sql) {
|
|
|
533
554
|
return trimmed.length === 0 ? "" : `${trimmed}\n`;
|
|
534
555
|
}
|
|
535
556
|
//#endregion
|
|
557
|
+
//#region src/schema/migration-post-process.ts
|
|
558
|
+
/**
|
|
559
|
+
* Run the steps that finish a migration Atlas just wrote — the search, vector
|
|
560
|
+
* and trigger DDL, the RLS bootstrap and policies, the re-hash — and take the
|
|
561
|
+
* migration back out when any of them fails.
|
|
562
|
+
*
|
|
563
|
+
* Atlas's own file creates the tables and nothing else: no `ENABLE ROW LEVEL
|
|
564
|
+
* SECURITY`, no policy. Those arrive in the append. A failure here used to be a
|
|
565
|
+
* yellow warning followed by "You can now run rebase db migrate", and when it
|
|
566
|
+
* struck before the file was rewritten the checksum still matched — so the
|
|
567
|
+
* migration applied cleanly and created every new table with RLS off while
|
|
568
|
+
* the schema's default privileges handed `rebase_user` full DML on it. On a
|
|
569
|
+
* deployment that runs migrations and never provisions at boot
|
|
570
|
+
* (`REBASE_MIGRATE_ON_BOOT=none`), nothing closed it again.
|
|
571
|
+
*
|
|
572
|
+
* Deleting the file is safe because it is the one this run created: running
|
|
573
|
+
* `db generate` again, once the cause is fixed, writes the same diff.
|
|
574
|
+
*/
|
|
575
|
+
async function finishNewMigration(file, steps, rehash) {
|
|
576
|
+
try {
|
|
577
|
+
await steps();
|
|
578
|
+
return { ok: true };
|
|
579
|
+
} catch (err) {
|
|
580
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
581
|
+
let removed = false;
|
|
582
|
+
try {
|
|
583
|
+
if (fs.existsSync(file)) fs.unlinkSync(file);
|
|
584
|
+
removed = !fs.existsSync(file);
|
|
585
|
+
} catch {
|
|
586
|
+
removed = false;
|
|
587
|
+
}
|
|
588
|
+
if (!removed) return {
|
|
589
|
+
ok: false,
|
|
590
|
+
error,
|
|
591
|
+
removed
|
|
592
|
+
};
|
|
593
|
+
try {
|
|
594
|
+
await rehash();
|
|
595
|
+
return {
|
|
596
|
+
ok: false,
|
|
597
|
+
error,
|
|
598
|
+
removed
|
|
599
|
+
};
|
|
600
|
+
} catch (hashErr) {
|
|
601
|
+
return {
|
|
602
|
+
ok: false,
|
|
603
|
+
error,
|
|
604
|
+
removed,
|
|
605
|
+
rehashError: hashErr instanceof Error ? hashErr.message : String(hashErr)
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
//#endregion
|
|
536
611
|
//#region src/schema/atlas-argv.ts
|
|
537
612
|
/**
|
|
538
613
|
* Does this Atlas invocation accept `--exclude`?
|
|
@@ -627,6 +702,90 @@ var ScratchDatabase = class {
|
|
|
627
702
|
}
|
|
628
703
|
};
|
|
629
704
|
//#endregion
|
|
705
|
+
//#region src/cli-push.ts
|
|
706
|
+
/**
|
|
707
|
+
* Apply a planned push.
|
|
708
|
+
*
|
|
709
|
+
* A dry-run that answered "Schema is synced" skips the second Atlas run: that
|
|
710
|
+
* run would plan the same diff again (≈0.9 s at 111 tables, 5.5 s at 411 —
|
|
711
|
+
* Atlas's cost grows faster than the table count) and then apply nothing. The
|
|
712
|
+
* steps after it still run, because Atlas cannot see what they own. A plan with
|
|
713
|
+
* anything in it keeps the old flow, including Atlas re-planning at apply time.
|
|
714
|
+
*
|
|
715
|
+
* @returns `"synced"` when Atlas was not run again, `"applied"` otherwise.
|
|
716
|
+
*/
|
|
717
|
+
async function applyPushPlan(plan, steps) {
|
|
718
|
+
const synced = isSyncedPlan(plan.dryRun) && plan.rebuildable === 0;
|
|
719
|
+
if (synced) steps.reportSynced();
|
|
720
|
+
else {
|
|
721
|
+
if (plan.rebuildable > 0) await steps.dropRebuildable();
|
|
722
|
+
try {
|
|
723
|
+
await steps.atlasApply();
|
|
724
|
+
} catch (err) {
|
|
725
|
+
if (plan.rebuildable > 0) await steps.rebuildAfterFailure();
|
|
726
|
+
throw err;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
await steps.afterApply();
|
|
730
|
+
return synced ? "synced" : "applied";
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* The tables whose stamp `db push` may write: those every one of whose planned
|
|
734
|
+
* statements `policies.sql` carries, verbatim.
|
|
735
|
+
*
|
|
736
|
+
* A stamp says "this stored definition is what the plan's statement made". It
|
|
737
|
+
* is only true of a policy the statement *did* make — and since both are
|
|
738
|
+
* rendered by the same function from the same collections, a table whose
|
|
739
|
+
* statements are all in the file is one whose policies the file just created.
|
|
740
|
+
* Anything else (a hand-edited file, a file generated elsewhere) is not
|
|
741
|
+
* stamped, and the next boot re-applies that table, as before.
|
|
742
|
+
*/
|
|
743
|
+
function stampableTables(plans, policiesSql) {
|
|
744
|
+
return plans.filter((plan) => plan.policyStatements.length > 0 && plan.policyStatements.every((statement) => policiesSql.includes(statement)));
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Run `policies.sql`, then stamp the policies it created the way a boot does,
|
|
748
|
+
* in the same transaction.
|
|
749
|
+
*
|
|
750
|
+
* Before stamps the push's policies carried none, so the first boot after
|
|
751
|
+
* every push re-applied them all — a DROP and a CREATE per policy, an ACCESS
|
|
752
|
+
* EXCLUSIVE lock per table. Stamped here, that boot finds them current.
|
|
753
|
+
*
|
|
754
|
+
* The file runs in one transaction, as it did as one implicit one: a policy
|
|
755
|
+
* that fails leaves the previous ones in place. The stamps run after it, under
|
|
756
|
+
* a savepoint: a stamp that cannot be written costs the next boot a re-apply,
|
|
757
|
+
* which is what it did before, so it is reported and the policies are kept.
|
|
758
|
+
* Nothing is left set on the connection: `SET LOCAL` ends with the transaction.
|
|
759
|
+
*/
|
|
760
|
+
async function applyPoliciesFile(client, policiesSql, stamping) {
|
|
761
|
+
await client.query("BEGIN");
|
|
762
|
+
let stamped = 0;
|
|
763
|
+
let stampError;
|
|
764
|
+
try {
|
|
765
|
+
await client.query(policiesSql);
|
|
766
|
+
if (stamping.statements.length > 0) {
|
|
767
|
+
await client.query("SAVEPOINT rebase_policy_stamps");
|
|
768
|
+
try {
|
|
769
|
+
if (stamping.searchPath) await client.query(`SET LOCAL search_path TO ${stamping.searchPath}`);
|
|
770
|
+
await client.query(stamping.statements.join("\n"));
|
|
771
|
+
await client.query("RELEASE SAVEPOINT rebase_policy_stamps");
|
|
772
|
+
stamped = stamping.statements.length;
|
|
773
|
+
} catch (err) {
|
|
774
|
+
await client.query("ROLLBACK TO SAVEPOINT rebase_policy_stamps");
|
|
775
|
+
stampError = err instanceof Error ? err.message : String(err);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
await client.query("COMMIT");
|
|
779
|
+
} catch (err) {
|
|
780
|
+
await client.query("ROLLBACK").catch(() => void 0);
|
|
781
|
+
throw err;
|
|
782
|
+
}
|
|
783
|
+
return {
|
|
784
|
+
stamped,
|
|
785
|
+
stampError
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
//#endregion
|
|
630
789
|
//#region src/branch-argv.ts
|
|
631
790
|
/**
|
|
632
791
|
* Argument shapes for `rebase db branch`, with no dependencies.
|
|
@@ -841,6 +1000,11 @@ var __cliDirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
841
1000
|
* tsx maps it back. Running the built `.js` under plain `node` therefore fails
|
|
842
1001
|
* on the user's code rather than on ours, one directory further from anything
|
|
843
1002
|
* the error mentions.
|
|
1003
|
+
*
|
|
1004
|
+
* tsx's loader in one Node (`node --import`) when it resolves, rather than the
|
|
1005
|
+
* `tsx` binary, which is a shell shim and a relay Node in front of that same
|
|
1006
|
+
* loader (see `tsx-loader.ts`). The binary stays the fallback — a global tsx.
|
|
1007
|
+
* `args` is everything after `bin`, the script included.
|
|
844
1008
|
*/
|
|
845
1009
|
function resolveChildScript(name) {
|
|
846
1010
|
const built = path.join(__cliDirname, "schema", `${name}.js`);
|
|
@@ -850,6 +1014,16 @@ function resolveChildScript(name) {
|
|
|
850
1014
|
ok: false,
|
|
851
1015
|
reason: "script"
|
|
852
1016
|
};
|
|
1017
|
+
const tsxLoader = resolveTsxLoader();
|
|
1018
|
+
if (tsxLoader) return {
|
|
1019
|
+
ok: true,
|
|
1020
|
+
bin: process.execPath,
|
|
1021
|
+
args: [
|
|
1022
|
+
"--import",
|
|
1023
|
+
tsxLoader,
|
|
1024
|
+
script
|
|
1025
|
+
]
|
|
1026
|
+
};
|
|
853
1027
|
const tsxBin = resolveLocalBin("tsx");
|
|
854
1028
|
if (!tsxBin) return {
|
|
855
1029
|
ok: false,
|
|
@@ -858,7 +1032,7 @@ function resolveChildScript(name) {
|
|
|
858
1032
|
return {
|
|
859
1033
|
ok: true,
|
|
860
1034
|
bin: tsxBin,
|
|
861
|
-
script
|
|
1035
|
+
args: [script]
|
|
862
1036
|
};
|
|
863
1037
|
}
|
|
864
1038
|
/** What to print when {@link resolveChildScript} cannot produce a command. */
|
|
@@ -866,6 +1040,40 @@ function childScriptError(what, reason) {
|
|
|
866
1040
|
if (reason === "tsx") return `✗ Could not find the tsx binary, which ${what} needs to read your collection files. Install your project's dependencies and try again.`;
|
|
867
1041
|
return `✗ The driver's ${what} is missing. This copy of @rebasepro/server-postgres is incomplete — reinstall it, or run its build if you are working in the monorepo.`;
|
|
868
1042
|
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Whether this process can run the generators itself — that is, whether it
|
|
1045
|
+
* runs under tsx, the interpreter the project's collection files need (see
|
|
1046
|
+
* {@link resolveChildScript}). `rebase` always starts the driver that way when
|
|
1047
|
+
* tsx is installed, which a scaffold guarantees; a driver started with plain
|
|
1048
|
+
* `node` keeps the child process, which brings its own tsx.
|
|
1049
|
+
*/
|
|
1050
|
+
function generatesInProcess() {
|
|
1051
|
+
return [...process.execArgv, process.env.NODE_OPTIONS ?? ""].some((flag) => /(?:^|[\s\\/=])tsx(?:$|[\s\\/@])/.test(flag));
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Run one of the generators in this process — see `schema/generate-files.ts`
|
|
1055
|
+
* for why a one-shot generation no longer gets a process of its own.
|
|
1056
|
+
*
|
|
1057
|
+
* The messages and the exit code are the child's: a generator that failed has
|
|
1058
|
+
* printed its own error, and this adds the one-line verdict; one that could not
|
|
1059
|
+
* even be loaded is reported the way a child that never started was.
|
|
1060
|
+
*/
|
|
1061
|
+
async function generateInProcess(what, label, run) {
|
|
1062
|
+
let generators;
|
|
1063
|
+
try {
|
|
1064
|
+
generators = await import("./generate-files-CdEckX6c.js").then((n) => n.t);
|
|
1065
|
+
} catch (err) {
|
|
1066
|
+
outError(chalk.red(`✗ Failed to run ${what}: ${err instanceof Error ? err.message : String(err)}`));
|
|
1067
|
+
process.exit(1);
|
|
1068
|
+
}
|
|
1069
|
+
try {
|
|
1070
|
+
await run(generators);
|
|
1071
|
+
} catch (err) {
|
|
1072
|
+
if (err instanceof generators.GenerationFailed) outError(chalk.red(`✗ ${label} generation failed — nothing was applied.`));
|
|
1073
|
+
else outError(chalk.red(`✗ Failed to run ${what}: ${err instanceof Error ? err.message : String(err)}`));
|
|
1074
|
+
process.exit(1);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
869
1077
|
async function loadEnv() {
|
|
870
1078
|
try {
|
|
871
1079
|
const dotenv = await import("dotenv");
|
|
@@ -894,12 +1102,19 @@ async function runPluginCommand(args) {
|
|
|
894
1102
|
const subcommand = args[1];
|
|
895
1103
|
assertKnownFlags(domain, subcommand, args);
|
|
896
1104
|
assertCollectionsPathExists(collectionsPathIn(args));
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1105
|
+
try {
|
|
1106
|
+
if (domain === "db") await scratchDatabase.around(() => dbCommand(subcommand, args));
|
|
1107
|
+
else if (domain === "schema") await schemaCommand(subcommand, args);
|
|
1108
|
+
else if (domain === "doctor") await doctorPluginCommand(args);
|
|
1109
|
+
else if (domain === "console") {
|
|
1110
|
+
const { consoleCommand } = await import("./cli-console-Ca6ZlAVU.js");
|
|
1111
|
+
process.exitCode = await consoleCommand(args);
|
|
1112
|
+
} else {
|
|
1113
|
+
outError(chalk.red(`Unknown domain command: ${domain}`));
|
|
1114
|
+
process.exit(1);
|
|
1115
|
+
}
|
|
1116
|
+
} finally {
|
|
1117
|
+
await closeAppClients();
|
|
903
1118
|
}
|
|
904
1119
|
}
|
|
905
1120
|
/** The migration files on disk, sorted, or none if the directory is absent. */
|
|
@@ -972,6 +1187,48 @@ async function stripCarvedOutFromNewestMigration(collectionsPath) {
|
|
|
972
1187
|
return !result.empty;
|
|
973
1188
|
}
|
|
974
1189
|
/**
|
|
1190
|
+
* One line per destructive statement, as `db push` prints them — shared by
|
|
1191
|
+
* push, `db generate` and `db migrate --refuse-destructive`, so the three
|
|
1192
|
+
* describe the same statement the same way.
|
|
1193
|
+
*/
|
|
1194
|
+
function destructiveLines(destructive) {
|
|
1195
|
+
return destructive.map((d) => chalk.red(` ${d.kind}${d.detail ? ` (${d.detail})` : ""}: `) + chalk.gray(d.statement.replace(/\s+/g, " ")));
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* The column types `detectDestructiveStatements` reads a type change against,
|
|
1199
|
+
* or none when the database cannot be asked — every type change then counts
|
|
1200
|
+
* as destructive, which is the safe side of a report.
|
|
1201
|
+
*/
|
|
1202
|
+
async function columnTypesOrNone(databaseUrl) {
|
|
1203
|
+
if (!databaseUrl) return [];
|
|
1204
|
+
try {
|
|
1205
|
+
return await queryColumnTypes(databaseUrl);
|
|
1206
|
+
} catch {
|
|
1207
|
+
return [];
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Say what in the migration `db generate` just wrote destroys data.
|
|
1212
|
+
*
|
|
1213
|
+
* `db push` stops before a destructive change; `db generate` wrote the same
|
|
1214
|
+
* `DROP COLUMN` into a file without a word, and `db migrate` applied it. Only
|
|
1215
|
+
* the part Atlas planned is read: what is appended after it (the search
|
|
1216
|
+
* rebuild, policies, triggers) is Rebase's own and puts back what it drops.
|
|
1217
|
+
* A report, not a refusal — the exit code is unchanged.
|
|
1218
|
+
*/
|
|
1219
|
+
async function reportDestructiveMigration(migrationSql, migrationFile, databaseUrl) {
|
|
1220
|
+
const destructive = detectDestructiveStatements(migrationSql, await columnTypesOrNone(databaseUrl));
|
|
1221
|
+
if (destructive.length === 0) return;
|
|
1222
|
+
outWarn("");
|
|
1223
|
+
outWarn(chalk.yellow(` ⚠️ ${migrationFile} includes ${destructive.length} destructive change(s) that will DESTROY data when applied:`));
|
|
1224
|
+
outWarn("");
|
|
1225
|
+
for (const line of destructiveLines(destructive)) outWarn(line);
|
|
1226
|
+
outWarn("");
|
|
1227
|
+
outWarn(chalk.yellow(" Review it before `rebase db migrate`. `rebase db migrate --refuse-destructive` will not apply it."));
|
|
1228
|
+
outWarn(chalk.yellow(" Back up first: rebase db backup"));
|
|
1229
|
+
outWarn("");
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
975
1232
|
* The flags `rebase db` reads for the subcommands it parses itself. Hoisted
|
|
976
1233
|
* and named `…FLAGS` so the documentation verifier, which reads specs out of
|
|
977
1234
|
* this source, sees them.
|
|
@@ -979,6 +1236,7 @@ async function stripCarvedOutFromNewestMigration(collectionsPath) {
|
|
|
979
1236
|
var DB_FLAGS = {
|
|
980
1237
|
"--collections": String,
|
|
981
1238
|
"--allow-destructive": Boolean,
|
|
1239
|
+
"--refuse-destructive": Boolean,
|
|
982
1240
|
"--dry-run": Boolean,
|
|
983
1241
|
"--yes": Boolean,
|
|
984
1242
|
"--baseline": String,
|
|
@@ -1011,7 +1269,7 @@ async function dbCommand(subcommand, rawArgs) {
|
|
|
1011
1269
|
return;
|
|
1012
1270
|
}
|
|
1013
1271
|
if (subcommand === "backup" || subcommand === "restore" || subcommand === "backups") {
|
|
1014
|
-
const { backupCommand, restoreCommand, backupsCommand } = await import("./backup-cli-
|
|
1272
|
+
const { backupCommand, restoreCommand, backupsCommand } = await import("./backup-cli-DnpJi6I7.js");
|
|
1015
1273
|
if (subcommand === "backup" && backupActionOf(rawArgs) === "list") await backupsCommand(rawArgs);
|
|
1016
1274
|
else if (subcommand === "backup") await backupCommand(rawArgs);
|
|
1017
1275
|
else if (subcommand === "restore") await restoreCommand(rawArgs);
|
|
@@ -1025,7 +1283,7 @@ async function dbCommand(subcommand, rawArgs) {
|
|
|
1025
1283
|
out(chalk.bold(" 📦 Rebase DB Generate"));
|
|
1026
1284
|
out(chalk.gray(" Step 1/2: Generating Drizzle schema & Postgres DDL from collections..."));
|
|
1027
1285
|
out("");
|
|
1028
|
-
await schemaCommand("generate", rawArgs);
|
|
1286
|
+
await schemaCommand("generate", rawArgs, { nextStep: false });
|
|
1029
1287
|
await generatePostgresDdlCommand(rawArgs);
|
|
1030
1288
|
out("");
|
|
1031
1289
|
out(chalk.gray(" Step 2/2: Generating SQL migration files with Atlas..."));
|
|
@@ -1042,54 +1300,65 @@ async function dbCommand(subcommand, rawArgs) {
|
|
|
1042
1300
|
], collectionsPath);
|
|
1043
1301
|
let wroteNewMigration = listMigrationFiles().length > migrationsBefore.length;
|
|
1044
1302
|
if (wroteNewMigration) wroteNewMigration = await stripCarvedOutFromNewestMigration(collectionsPath);
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
}
|
|
1067
|
-
migrationContent = `${migrationContent}\n\n${searchContent}`;
|
|
1068
|
-
out(chalk.gray(" ✓ Appended search DDL to the migration"));
|
|
1069
|
-
}
|
|
1070
|
-
const triggersContent = readTriggersDdl();
|
|
1071
|
-
if (triggersContent) {
|
|
1072
|
-
migrationContent = `${migrationContent}\n\n${triggersContent}`;
|
|
1073
|
-
out(chalk.gray(" ✓ Appended `updated_at` triggers to the migration"));
|
|
1074
|
-
}
|
|
1075
|
-
fs.writeFileSync(newestMigrationFile, migrationContent, "utf-8");
|
|
1076
|
-
const policiesFile = path.join(generatedSqlDir(), "policies.sql");
|
|
1077
|
-
if (fs.existsSync(policiesFile)) {
|
|
1078
|
-
const policiesContent = fs.readFileSync(policiesFile, "utf-8");
|
|
1079
|
-
fs.appendFileSync(newestMigrationFile, "\n\n" + RLS_BOOTSTRAP_SQL + "\n" + policiesContent);
|
|
1080
|
-
out(chalk.gray(` ✓ Appended RLS policies to migration file: ${path.basename(newestMigrationFile)}`));
|
|
1081
|
-
out(chalk.gray(" Re-hashing migration files..."));
|
|
1082
|
-
await runAtlas("migrate", [
|
|
1083
|
-
"hash",
|
|
1084
|
-
"--dir",
|
|
1085
|
-
"file://drizzle/migrations"
|
|
1086
|
-
], collectionsPath);
|
|
1087
|
-
out(chalk.gray(" ✓ Migration directory checksum updated successfully."));
|
|
1303
|
+
const newestWritten = wroteNewMigration ? listMigrationFiles().at(-1) : void 0;
|
|
1304
|
+
if (newestWritten) await reportDestructiveMigration(fs.readFileSync(path.resolve(process.cwd(), "drizzle", "migrations", newestWritten), "utf-8"), newestWritten, process.env.DATABASE_URL);
|
|
1305
|
+
const migrationsDir = path.resolve(process.cwd(), "drizzle", "migrations");
|
|
1306
|
+
const newestSql = fs.existsSync(migrationsDir) && wroteNewMigration ? fs.readdirSync(migrationsDir).filter((f) => f.endsWith(".sql")).sort().at(-1) : void 0;
|
|
1307
|
+
if (newestSql) {
|
|
1308
|
+
const newestMigrationFile = path.join(migrationsDir, newestSql);
|
|
1309
|
+
const finished = await finishNewMigration(newestMigrationFile, async () => {
|
|
1310
|
+
let migrationContent = fs.readFileSync(newestMigrationFile, "utf-8");
|
|
1311
|
+
migrationContent = migrationContent.replace(/CREATE SCHEMA (?!IF NOT EXISTS)("[^"]+");/g, "CREATE SCHEMA IF NOT EXISTS $1;");
|
|
1312
|
+
const vectorContent = readVectorDdl();
|
|
1313
|
+
if (vectorContent) {
|
|
1314
|
+
migrationContent = `${migrationContent}\n\n${vectorContent}`;
|
|
1315
|
+
out(chalk.gray(" ✓ Appended vector DDL to the migration"));
|
|
1316
|
+
}
|
|
1317
|
+
const searchContent = readSearchDdl();
|
|
1318
|
+
if (searchContent) {
|
|
1319
|
+
const { searchColumnDependencies } = await import("./generate-postgres-ddl-logic-B64O2oZF.js").then((n) => n.o);
|
|
1320
|
+
const migrationConflicts = findGeneratedColumnConflicts(parseColumnMutations(migrationContent), searchColumnDependencies(await loadCollectionsForCli(collectionsPath)));
|
|
1321
|
+
const preamble = migrationDropPreamble(migrationConflicts);
|
|
1322
|
+
if (preamble) {
|
|
1323
|
+
migrationContent = `${preamble}\n${migrationContent}`;
|
|
1324
|
+
for (const conflict of migrationConflicts) out(chalk.gray(` ✓ ${describeConflict(conflict)} is dropped and rebuilt inside the migration`));
|
|
1088
1325
|
}
|
|
1326
|
+
migrationContent = `${migrationContent}\n\n${searchContent}`;
|
|
1327
|
+
out(chalk.gray(" ✓ Appended search DDL to the migration"));
|
|
1328
|
+
}
|
|
1329
|
+
const triggersContent = readTriggersDdl();
|
|
1330
|
+
if (triggersContent) {
|
|
1331
|
+
migrationContent = `${migrationContent}\n\n${triggersContent}`;
|
|
1332
|
+
out(chalk.gray(" ✓ Appended `updated_at` triggers to the migration"));
|
|
1089
1333
|
}
|
|
1334
|
+
fs.writeFileSync(newestMigrationFile, migrationContent, "utf-8");
|
|
1335
|
+
const policiesFile = path.join(generatedSqlDir(), "policies.sql");
|
|
1336
|
+
if (fs.existsSync(policiesFile)) {
|
|
1337
|
+
const policiesContent = fs.readFileSync(policiesFile, "utf-8");
|
|
1338
|
+
fs.appendFileSync(newestMigrationFile, "\n\n" + RLS_BOOTSTRAP_SQL + "\n" + policiesContent);
|
|
1339
|
+
out(chalk.gray(` ✓ Appended RLS policies to migration file: ${path.basename(newestMigrationFile)}`));
|
|
1340
|
+
out(chalk.gray(" Re-hashing migration files..."));
|
|
1341
|
+
await runAtlas("migrate", [
|
|
1342
|
+
"hash",
|
|
1343
|
+
"--dir",
|
|
1344
|
+
"file://drizzle/migrations"
|
|
1345
|
+
], collectionsPath, { throwOnFailure: true });
|
|
1346
|
+
out(chalk.gray(" ✓ Migration directory checksum updated successfully."));
|
|
1347
|
+
}
|
|
1348
|
+
}, async () => {
|
|
1349
|
+
await runAtlas("migrate", [
|
|
1350
|
+
"hash",
|
|
1351
|
+
"--dir",
|
|
1352
|
+
"file://drizzle/migrations"
|
|
1353
|
+
], collectionsPath, { throwOnFailure: true });
|
|
1354
|
+
});
|
|
1355
|
+
if (!finished.ok) {
|
|
1356
|
+
outError(chalk.red(`\n ✗ Could not finish ${newestSql}: ${finished.error}`));
|
|
1357
|
+
outError(chalk.gray(" Atlas's half of a migration creates the tables; the part that failed adds their\n row-level security, policies, search, vector and trigger DDL. Applied alone, it\n would serve the new tables with no row filtering."));
|
|
1358
|
+
outError(chalk.gray(finished.removed ? ` ${newestSql} was removed. Fix the cause and run \`rebase db generate\` again.` : ` ${newestSql} could not be removed — delete it by hand before \`rebase db migrate\`.`));
|
|
1359
|
+
if (finished.rehashError) outError(chalk.gray(` Re-hashing failed too (${finished.rehashError}); run \`atlas migrate hash\` or generate again.`));
|
|
1360
|
+
process.exit(1);
|
|
1090
1361
|
}
|
|
1091
|
-
} catch (err) {
|
|
1092
|
-
outWarn(chalk.yellow(` ⚠️ Failed to append policies or re-hash migration: ${err instanceof Error ? err.message : String(err)}`));
|
|
1093
1362
|
}
|
|
1094
1363
|
if (!wroteNewMigration) out(chalk.gray(` ℹ No migration written — nothing in this change is visible to Atlas.
|
|
1095
1364
|
Search, vector, trigger and RLS DDL are applied by \`rebase db push\`, and at
|
|
@@ -1103,16 +1372,19 @@ async function dbCommand(subcommand, rawArgs) {
|
|
|
1103
1372
|
out(chalk.bold(` 🗄️ Rebase DB ${subcommand.charAt(0).toUpperCase() + subcommand.slice(1)}`));
|
|
1104
1373
|
out("");
|
|
1105
1374
|
if (subcommand === "push") {
|
|
1106
|
-
|
|
1375
|
+
const dryRun = argsList["--dry-run"] === true;
|
|
1376
|
+
out(chalk.gray(dryRun ? " Step 1/3: Generating the Postgres DDL from collections (the plan's input)..." : " Step 1/3: Generating Drizzle schema & Postgres DDL from collections..."));
|
|
1107
1377
|
out("");
|
|
1108
|
-
|
|
1109
|
-
await
|
|
1378
|
+
if (dryRun) out(chalk.gray(" --dry-run: src/schema.generated.ts is left as it is."));
|
|
1379
|
+
else await schemaCommand("generate", rawArgs, { nextStep: false });
|
|
1380
|
+
await generatePostgresDdlCommand(rawArgs, { retireLegacyCopies: !dryRun });
|
|
1110
1381
|
out("");
|
|
1111
|
-
const dryRun = argsList["--dry-run"] === true;
|
|
1112
1382
|
out(chalk.gray(dryRun ? " Step 2/3: Planning the change against the database (nothing is applied)..." : " Step 2/3: Pushing schema to database with Atlas..."));
|
|
1113
1383
|
out("");
|
|
1114
1384
|
const databaseUrl = process.env.DATABASE_URL;
|
|
1115
1385
|
if (databaseUrl && !dryRun) await ensureAuthSchemaAndFunctions(databaseUrl);
|
|
1386
|
+
if (databaseUrl) await renameLegacyForeignKeys(databaseUrl, collectionsPath, dryRun);
|
|
1387
|
+
const prebuilt = databaseUrl ? await prebuildForeignKeyIndexes(databaseUrl, collectionsPath, dryRun) : [];
|
|
1116
1388
|
const plan = await runAtlas("schema", [
|
|
1117
1389
|
"apply",
|
|
1118
1390
|
"--to",
|
|
@@ -1124,6 +1396,7 @@ async function dbCommand(subcommand, rawArgs) {
|
|
|
1124
1396
|
const planStatements = extractPlanStatements(plan);
|
|
1125
1397
|
const planListing = unreadable ? plan.trim() : planStatements.map((statement) => `${statement};`).join("\n");
|
|
1126
1398
|
const { managed: rebuildable, foreign: unowned } = partitionByOwnership(databaseUrl ? findGeneratedColumnConflicts(parseColumnMutations(plan), await queryGeneratedColumnDependencies(databaseUrl)) : [], collectionsPath ? await getSearchExcludes(collectionsPath) : []);
|
|
1399
|
+
if (databaseUrl) await reportKeptEnumLabels(databaseUrl, collectionsPath);
|
|
1127
1400
|
if (dryRun) {
|
|
1128
1401
|
out(chalk.gray(" Step 3/3: --dry-run — nothing was applied."));
|
|
1129
1402
|
out("");
|
|
@@ -1132,6 +1405,13 @@ async function dbCommand(subcommand, rawArgs) {
|
|
|
1132
1405
|
out(chalk.gray(planListing.split("\n").map((l) => ` ${l}`).join("\n")));
|
|
1133
1406
|
} else out(chalk.green(" ✓ No changes: the database already matches these collections."));
|
|
1134
1407
|
out("");
|
|
1408
|
+
if (prebuilt.length > 0) {
|
|
1409
|
+
out(chalk.gray(` ${prebuilt.length} foreign-key index(es) on existing tables would be built first,`));
|
|
1410
|
+
out(chalk.gray(" CONCURRENTLY, so the apply would not build them under a write lock (the plan"));
|
|
1411
|
+
out(chalk.gray(" above lists them because they do not exist yet):"));
|
|
1412
|
+
for (const name of prebuilt) out(chalk.gray(` ${name}`));
|
|
1413
|
+
out("");
|
|
1414
|
+
}
|
|
1135
1415
|
if (rebuildable.length > 0) {
|
|
1136
1416
|
out(chalk.gray(` ${rebuildable.length} generated column(s) would be dropped before the apply and`));
|
|
1137
1417
|
out(chalk.gray(" rebuilt from search.sql after it — Postgres cannot retype a column"));
|
|
@@ -1142,7 +1422,7 @@ async function dbCommand(subcommand, rawArgs) {
|
|
|
1142
1422
|
if (unowned.length > 0) outWarn(formatForeignGeneratedColumnBanner(unowned));
|
|
1143
1423
|
if (destructive.length > 0) {
|
|
1144
1424
|
outWarn(chalk.yellow(` ⚠️ ${destructive.length} of those DESTROY data:`));
|
|
1145
|
-
for (const
|
|
1425
|
+
for (const line of destructiveLines(destructive)) outWarn(line);
|
|
1146
1426
|
outWarn("");
|
|
1147
1427
|
outWarn(chalk.yellow(" Applying them needs `rebase db push --allow-destructive`. Back up first: rebase db backup"));
|
|
1148
1428
|
outWarn("");
|
|
@@ -1164,7 +1444,7 @@ async function dbCommand(subcommand, rawArgs) {
|
|
|
1164
1444
|
if (destructive.length > 0) {
|
|
1165
1445
|
outWarn(chalk.yellow(` ⚠️ This push includes ${destructive.length} destructive change(s) that will DESTROY data:`));
|
|
1166
1446
|
outWarn("");
|
|
1167
|
-
for (const
|
|
1447
|
+
for (const line of destructiveLines(destructive)) outWarn(line);
|
|
1168
1448
|
outWarn("");
|
|
1169
1449
|
outWarn(chalk.yellow(" Full planned changes:"));
|
|
1170
1450
|
outWarn(chalk.gray(planListing.split("\n").map((l) => ` ${l}`).join("\n")));
|
|
@@ -1181,40 +1461,65 @@ async function dbCommand(subcommand, rawArgs) {
|
|
|
1181
1461
|
}
|
|
1182
1462
|
} else outWarn(chalk.yellow(" Proceeding because --allow-destructive was passed."));
|
|
1183
1463
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1464
|
+
await applyPushPlan({
|
|
1465
|
+
dryRun: plan,
|
|
1466
|
+
rebuildable: databaseUrl ? rebuildable.length : 0
|
|
1467
|
+
}, {
|
|
1468
|
+
dropRebuildable: async () => {
|
|
1469
|
+
if (!databaseUrl) return;
|
|
1470
|
+
for (const conflict of rebuildable) out(chalk.gray(` Dropping ${describeConflict(conflict)} — it reads a column this push retypes.`));
|
|
1471
|
+
await dropGeneratedColumns(databaseUrl, dropGeneratedColumnStatements(rebuildable));
|
|
1472
|
+
},
|
|
1473
|
+
atlasApply: async () => {
|
|
1474
|
+
await runAtlas("schema", [
|
|
1475
|
+
"apply",
|
|
1476
|
+
"--to",
|
|
1477
|
+
DESIRED_STATE_URL,
|
|
1478
|
+
"--auto-approve"
|
|
1479
|
+
], collectionsPath, { throwOnFailure: true });
|
|
1480
|
+
},
|
|
1481
|
+
reportSynced: () => out("Schema is synced, no changes to be made"),
|
|
1482
|
+
rebuildAfterFailure: async () => {
|
|
1483
|
+
if (!databaseUrl) return;
|
|
1197
1484
|
out(chalk.gray(" The apply failed — rebuilding the generated columns it needed out of the way."));
|
|
1198
1485
|
await applySearchDdl(databaseUrl);
|
|
1486
|
+
},
|
|
1487
|
+
afterApply: async () => {
|
|
1488
|
+
out("");
|
|
1489
|
+
if (!databaseUrl) {
|
|
1490
|
+
outWarn(chalk.yellow(" ⚠️ DATABASE_URL not found in environment, skipping RLS policies application."));
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
await lockManagedTables(databaseUrl, collectionsPath);
|
|
1494
|
+
await ensureAuthTables(databaseUrl, collectionsPath);
|
|
1495
|
+
await applyVectorDdl(databaseUrl);
|
|
1496
|
+
await applySearchDdl(databaseUrl);
|
|
1497
|
+
await applyTriggersDdl(databaseUrl);
|
|
1498
|
+
await applyPolicies(databaseUrl, collectionsPath);
|
|
1499
|
+
await reconcilePolicies(databaseUrl, collectionsPath);
|
|
1500
|
+
await ensureRlsUserRole(databaseUrl);
|
|
1501
|
+
await retireLegacyAuthSchema(databaseUrl);
|
|
1199
1502
|
}
|
|
1200
|
-
|
|
1201
|
-
}
|
|
1202
|
-
out("");
|
|
1203
|
-
if (databaseUrl) {
|
|
1204
|
-
await lockManagedTables(databaseUrl, collectionsPath);
|
|
1205
|
-
await ensureAuthTables(databaseUrl, collectionsPath);
|
|
1206
|
-
await applyVectorDdl(databaseUrl);
|
|
1207
|
-
await applySearchDdl(databaseUrl);
|
|
1208
|
-
await applyTriggersDdl(databaseUrl);
|
|
1209
|
-
await applyPolicies(databaseUrl);
|
|
1210
|
-
await reconcilePolicies(databaseUrl, collectionsPath);
|
|
1211
|
-
await ensureRlsUserRole(databaseUrl);
|
|
1212
|
-
await retireLegacyAuthSchema(databaseUrl);
|
|
1213
|
-
} else outWarn(chalk.yellow(" ⚠️ DATABASE_URL not found in environment, skipping RLS policies application."));
|
|
1503
|
+
});
|
|
1214
1504
|
} else if (subcommand === "migrate") {
|
|
1215
1505
|
const databaseUrl = process.env.DATABASE_URL;
|
|
1216
|
-
if (databaseUrl) await ensureAuthSchemaAndFunctions(databaseUrl);
|
|
1217
1506
|
const baseline = argsList["--baseline"];
|
|
1507
|
+
if (argsList["--refuse-destructive"] === true) {
|
|
1508
|
+
const plan = await runAtlas("migrate", [...migrateApplyArgs(argsList._, baseline), "--dry-run"], collectionsPath, { captureStdout: true });
|
|
1509
|
+
const destructive = detectDestructiveStatements(plan, await columnTypesOrNone(databaseUrl));
|
|
1510
|
+
if (destructive.length > 0) {
|
|
1511
|
+
outError(chalk.red(` ✗ Refusing to migrate: the pending migrations include ${destructive.length} destructive change(s):`));
|
|
1512
|
+
outError("");
|
|
1513
|
+
for (const line of destructiveLines(destructive)) outError(line);
|
|
1514
|
+
outError("");
|
|
1515
|
+
outError(chalk.gray(" Nothing was applied. Run `rebase db migrate` without --refuse-destructive to apply them."));
|
|
1516
|
+
outError(chalk.gray(" Back up first: rebase db backup"));
|
|
1517
|
+
process.exit(1);
|
|
1518
|
+
}
|
|
1519
|
+
out(chalk.gray(` --refuse-destructive: ${extractPlanStatements(plan).length} pending statement(s), none destructive.`));
|
|
1520
|
+
out("");
|
|
1521
|
+
}
|
|
1522
|
+
if (databaseUrl) await ensureAuthSchemaAndFunctions(databaseUrl);
|
|
1218
1523
|
if (baseline) {
|
|
1219
1524
|
out(chalk.gray(` Recording ${baseline} as already applied, then migrating from the next one.`));
|
|
1220
1525
|
out("");
|
|
@@ -1230,16 +1535,79 @@ async function dbCommand(subcommand, rawArgs) {
|
|
|
1230
1535
|
out("");
|
|
1231
1536
|
}
|
|
1232
1537
|
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Build, CONCURRENTLY and before Atlas plans, the derived foreign-key indexes
|
|
1540
|
+
* an existing table is missing — see `schema/push-index-prebuild.ts`. With
|
|
1541
|
+
* `dryRun`, only find them.
|
|
1542
|
+
*
|
|
1543
|
+
* Never fatal: an index not built here is one Atlas builds inside the push, as
|
|
1544
|
+
* it always did.
|
|
1545
|
+
*
|
|
1546
|
+
* @returns `schema.index` of each one missing when this started.
|
|
1547
|
+
*/
|
|
1548
|
+
/** Renames relation columns that carry their pre-0.13 derived name, as boot does; a dry-run only names them. */
|
|
1549
|
+
async function renameLegacyForeignKeys(databaseUrl, collectionsPath, dryRun) {
|
|
1550
|
+
let renames;
|
|
1551
|
+
try {
|
|
1552
|
+
const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
|
|
1553
|
+
const { legacyForeignKeyRenames } = await import("./push-legacy-renames-BK6ilQq2.js");
|
|
1554
|
+
const collections = await loadCollections(path.resolve(process.cwd(), collectionsPath));
|
|
1555
|
+
renames = await withAppClient(databaseUrl, (client) => legacyForeignKeyRenames({ query: async (text) => ({ rows: (await client.query(text)).rows }) }, collections));
|
|
1556
|
+
} catch {
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1559
|
+
if (renames.length === 0) return;
|
|
1560
|
+
out(chalk.gray(dryRun ? ` ${renames.length} relation column(s) carry the name Rebase derived before 0.13; a push renames them first,` : ` Renaming ${renames.length} relation column(s) that carry the name Rebase derived before 0.13 (data, indexes and`));
|
|
1561
|
+
out(chalk.gray(dryRun ? " keeping their data (the plan below still shows them as a drop and an add, because nothing ran):" : " constraints are kept — the same rename `rebase dev` runs):"));
|
|
1562
|
+
for (const rename of renames) out(chalk.gray(` ${rename.table}: "${rename.legacy}" → "${rename.expected}"`));
|
|
1563
|
+
if (!dryRun) await withAppClient(databaseUrl, async (client) => {
|
|
1564
|
+
for (const rename of renames) await client.query(rename.sql);
|
|
1565
|
+
});
|
|
1566
|
+
out(chalk.gray(" To keep an old name instead, name the column on the relation (`localKey`, or `through` on a many-to-many)."));
|
|
1567
|
+
out("");
|
|
1568
|
+
}
|
|
1569
|
+
/** Names the enum labels the database keeps after the collections dropped them. See `schema/enum-labels-kept.ts`. */
|
|
1570
|
+
async function reportKeptEnumLabels(databaseUrl, collectionsPath) {
|
|
1571
|
+
try {
|
|
1572
|
+
const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
|
|
1573
|
+
const { keptEnumLabels, formatKeptEnumLabelsNotice } = await import("./enum-labels-kept-D8fI1MoX.js");
|
|
1574
|
+
const collections = await loadCollections(path.resolve(process.cwd(), collectionsPath));
|
|
1575
|
+
const kept = await withAppClient(databaseUrl, (client) => keptEnumLabels({ query: async (text) => ({ rows: (await client.query(text)).rows }) }, collections));
|
|
1576
|
+
if (kept.length > 0) outWarn(chalk.yellow(formatKeptEnumLabelsNotice(kept)));
|
|
1577
|
+
} catch {}
|
|
1578
|
+
}
|
|
1579
|
+
async function prebuildForeignKeyIndexes(databaseUrl, collectionsPath, dryRun) {
|
|
1580
|
+
let missing;
|
|
1581
|
+
try {
|
|
1582
|
+
const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
|
|
1583
|
+
const { missingForeignKeyIndexes } = await import("./push-index-prebuild-Db4AQF4i.js");
|
|
1584
|
+
const collections = await loadCollections(path.resolve(process.cwd(), collectionsPath));
|
|
1585
|
+
missing = await withAppClient(databaseUrl, (client) => missingForeignKeyIndexes({ query: async (text) => ({ rows: (await client.query(text)).rows }) }, collections));
|
|
1586
|
+
} catch {
|
|
1587
|
+
return [];
|
|
1588
|
+
}
|
|
1589
|
+
const names = missing.map((index) => `${index.schema}.${index.indexName}`);
|
|
1590
|
+
if (missing.length === 0 || dryRun) return names;
|
|
1591
|
+
out(chalk.gray(` Building ${missing.length} foreign-key index(es) on existing tables first — CONCURRENTLY, so writes go on:`));
|
|
1592
|
+
try {
|
|
1593
|
+
const { buildDeferredIndexes } = await import("./deferred-index-build-DP1_SNFo.js").then((n) => n.r);
|
|
1594
|
+
const outcome = await withAppClient(databaseUrl, (client) => buildDeferredIndexes(async () => ({
|
|
1595
|
+
dedicated: true,
|
|
1596
|
+
query: async (text) => ({ rows: (await client.query(text)).rows }),
|
|
1597
|
+
release: () => void 0
|
|
1598
|
+
}), missing, { log: (message) => out(chalk.gray(` ${message}`)) }));
|
|
1599
|
+
for (const failure of outcome.failed) outWarn(chalk.yellow(` ⚠️ ${failure.index} was not built ahead (${failure.error}); the push builds it inside its transaction instead.`));
|
|
1600
|
+
} catch (err) {
|
|
1601
|
+
outWarn(chalk.yellow(` ⚠️ The foreign-key indexes were not built ahead (${err instanceof Error ? err.message : String(err)}); the push builds them inside its transaction instead.`));
|
|
1602
|
+
}
|
|
1603
|
+
out("");
|
|
1604
|
+
return names;
|
|
1605
|
+
}
|
|
1233
1606
|
async function ensureAuthSchemaAndFunctions(databaseUrl) {
|
|
1234
1607
|
try {
|
|
1235
|
-
|
|
1236
|
-
const client = new Client({ connectionString: databaseUrl });
|
|
1237
|
-
await client.connect();
|
|
1238
|
-
try {
|
|
1608
|
+
await withAppClient(databaseUrl, async (client) => {
|
|
1239
1609
|
await client.query(RLS_BOOTSTRAP_SQL);
|
|
1240
|
-
}
|
|
1241
|
-
await client.end();
|
|
1242
|
-
}
|
|
1610
|
+
});
|
|
1243
1611
|
} catch (err) {
|
|
1244
1612
|
outWarn(chalk.yellow(` ⚠️ Failed to bootstrap the RLS helper functions: ${err instanceof Error ? err.message : String(err)}`));
|
|
1245
1613
|
}
|
|
@@ -1266,18 +1634,11 @@ async function lockManagedTables(databaseUrl, collectionsPath) {
|
|
|
1266
1634
|
let unsecured;
|
|
1267
1635
|
let userRole;
|
|
1268
1636
|
try {
|
|
1269
|
-
const { lockCollectionTables } = await import("./ensure-collection-policies-
|
|
1270
|
-
const { loadCollections } = await import("./doctor-
|
|
1271
|
-
userRole = (await import("./rls-enforcement-
|
|
1637
|
+
const { lockCollectionTables } = await import("./ensure-collection-policies-CXpbeNag.js");
|
|
1638
|
+
const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
|
|
1639
|
+
userRole = (await import("./rls-enforcement-DvzqlaL1.js").then((n) => n.c)).REBASE_USER_ROLE;
|
|
1272
1640
|
const collections = await loadCollections(path.resolve(process.cwd(), collectionsPath));
|
|
1273
|
-
|
|
1274
|
-
const client = new Client({ connectionString: databaseUrl });
|
|
1275
|
-
await client.connect();
|
|
1276
|
-
try {
|
|
1277
|
-
({unsecured} = await lockCollectionTables({ query: async (text) => ({ rows: (await client.query(text)).rows }) }, collections));
|
|
1278
|
-
} finally {
|
|
1279
|
-
await client.end();
|
|
1280
|
-
}
|
|
1641
|
+
({unsecured} = await withAppClient(databaseUrl, (client) => lockCollectionTables({ query: async (text) => ({ rows: (await client.query(text)).rows }) }, collections)));
|
|
1281
1642
|
} catch (err) {
|
|
1282
1643
|
outError(chalk.red(`\n ✗ The schema was applied, but row-level security could not be switched on for its tables: ${err instanceof Error ? err.message : String(err)}`));
|
|
1283
1644
|
outError(chalk.gray(" A table this push created may be readable and writable by every authenticated request\n until it is. Re-run `rebase db push` — it locks every collection table before anything else."));
|
|
@@ -1310,20 +1671,13 @@ async function lockManagedTables(databaseUrl, collectionsPath) {
|
|
|
1310
1671
|
async function ensureAuthTables(databaseUrl, collectionsPath) {
|
|
1311
1672
|
try {
|
|
1312
1673
|
const { drizzle } = await import("drizzle-orm/node-postgres");
|
|
1313
|
-
const { ensureAuthTablesExist } = await import("./ensure-tables-
|
|
1314
|
-
const { loadCollections } = await import("./doctor-
|
|
1315
|
-
const { Client } = await import("pg");
|
|
1674
|
+
const { ensureAuthTablesExist } = await import("./ensure-tables-CVm_yvzb.js").then((n) => n.n);
|
|
1675
|
+
const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
|
|
1316
1676
|
const authCollection = (await loadCollections(path.resolve(process.cwd(), collectionsPath))).find((c) => {
|
|
1317
1677
|
const a = c.auth;
|
|
1318
1678
|
return a === true || typeof a === "object" && a !== null && a.enabled === true;
|
|
1319
1679
|
});
|
|
1320
|
-
|
|
1321
|
-
await client.connect();
|
|
1322
|
-
try {
|
|
1323
|
-
await ensureAuthTablesExist(drizzle(client), authCollection);
|
|
1324
|
-
} finally {
|
|
1325
|
-
await client.end();
|
|
1326
|
-
}
|
|
1680
|
+
await withAppClient(databaseUrl, (client) => ensureAuthTablesExist(drizzle(client), authCollection));
|
|
1327
1681
|
} catch (err) {
|
|
1328
1682
|
outWarn(chalk.yellow(` ⚠️ Failed to ensure framework auth tables: ${err instanceof Error ? err.message : String(err)}`));
|
|
1329
1683
|
}
|
|
@@ -1338,24 +1692,19 @@ async function ensureRlsUserRole(databaseUrl) {
|
|
|
1338
1692
|
const schemas = ["public", "rebase"];
|
|
1339
1693
|
let rls;
|
|
1340
1694
|
try {
|
|
1341
|
-
rls = await import("./rls-enforcement-
|
|
1695
|
+
rls = await import("./rls-enforcement-DvzqlaL1.js").then((n) => n.c);
|
|
1342
1696
|
} catch (err) {
|
|
1343
1697
|
outError(chalk.red(`\n ✗ Could not load the RLS provisioning module: ${err instanceof Error ? err.message : String(err)}`));
|
|
1344
1698
|
return;
|
|
1345
1699
|
}
|
|
1346
1700
|
try {
|
|
1347
|
-
|
|
1348
|
-
const client = new Client({ connectionString: databaseUrl });
|
|
1349
|
-
await client.connect();
|
|
1350
|
-
try {
|
|
1701
|
+
await withAppClient(databaseUrl, async (client) => {
|
|
1351
1702
|
const runSql = async (text) => (await client.query(text)).rows;
|
|
1352
1703
|
if ((await rls.detectConnectionPosture(runSql)).privileged) {
|
|
1353
1704
|
await rls.ensureAppRole(runSql, schemas);
|
|
1354
1705
|
out(chalk.gray(` ✓ RLS role "${rls.REBASE_USER_ROLE}" provisioned/refreshed.`));
|
|
1355
1706
|
}
|
|
1356
|
-
}
|
|
1357
|
-
await client.end();
|
|
1358
|
-
}
|
|
1707
|
+
});
|
|
1359
1708
|
} catch (err) {
|
|
1360
1709
|
outError(chalk.red(`\n ✗ The schema change was applied, but the "${rls.REBASE_USER_ROLE}" role could not be provisioned: ${err instanceof Error ? err.message : String(err)}`));
|
|
1361
1710
|
outError(chalk.gray(rls.appRoleSetupInstructions("your database user", schemas)));
|
|
@@ -1372,35 +1721,23 @@ async function ensureRlsUserRole(databaseUrl) {
|
|
|
1372
1721
|
*/
|
|
1373
1722
|
async function retireLegacyAuthSchema(databaseUrl) {
|
|
1374
1723
|
try {
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
await dropLegacyAuthSchema(async (text) => (await client.query(text)).rows, {
|
|
1380
|
-
info: (m) => out(chalk.gray(` ${m}`)),
|
|
1381
|
-
warn: (m) => outWarn(chalk.yellow(` ⚠️ ${m}`))
|
|
1382
|
-
});
|
|
1383
|
-
} finally {
|
|
1384
|
-
await client.end();
|
|
1385
|
-
}
|
|
1724
|
+
await withAppClient(databaseUrl, (client) => dropLegacyAuthSchema(async (text) => (await client.query(text)).rows, {
|
|
1725
|
+
info: (m) => out(chalk.gray(` ${m}`)),
|
|
1726
|
+
warn: (m) => outWarn(chalk.yellow(` ⚠️ ${m}`))
|
|
1727
|
+
}));
|
|
1386
1728
|
} catch {}
|
|
1387
1729
|
}
|
|
1388
|
-
async function applyPolicies(databaseUrl) {
|
|
1730
|
+
async function applyPolicies(databaseUrl, collectionsPath) {
|
|
1389
1731
|
try {
|
|
1390
1732
|
const policiesPath = path.join(generatedSqlDir(), "policies.sql");
|
|
1391
1733
|
if (!fs.existsSync(policiesPath)) return;
|
|
1392
1734
|
out(chalk.gray(" Step 3/3: Applying RLS policies to database..."));
|
|
1393
1735
|
out("");
|
|
1394
1736
|
const policiesContent = fs.readFileSync(policiesPath, "utf-8");
|
|
1395
|
-
const
|
|
1396
|
-
const
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
await client.query(policiesContent);
|
|
1400
|
-
out(chalk.green(" ✓ RLS policies applied successfully."));
|
|
1401
|
-
} finally {
|
|
1402
|
-
await client.end();
|
|
1403
|
-
}
|
|
1737
|
+
const stamping = await policyStamping(databaseUrl, collectionsPath, policiesContent);
|
|
1738
|
+
const { stampError } = await withAppClient(databaseUrl, (client) => applyPoliciesFile(client, policiesContent, stamping));
|
|
1739
|
+
out(chalk.green(" ✓ RLS policies applied successfully."));
|
|
1740
|
+
if (stampError) outWarn(chalk.yellow(` ⚠️ The policies could not be stamped (${stampError}); the next boot applies them again.`));
|
|
1404
1741
|
} catch (err) {
|
|
1405
1742
|
const hint = diagnoseDbError(err, databaseUrl);
|
|
1406
1743
|
if (hint) outError(hint);
|
|
@@ -1409,6 +1746,25 @@ async function applyPolicies(databaseUrl) {
|
|
|
1409
1746
|
}
|
|
1410
1747
|
}
|
|
1411
1748
|
/**
|
|
1749
|
+
* The stamps a boot looks for, for the policies `policies.sql` is about to
|
|
1750
|
+
* create — see `applyPoliciesFile`. None when the collections cannot be read
|
|
1751
|
+
* here, which costs only the shortcut: the next boot re-applies, as before.
|
|
1752
|
+
*/
|
|
1753
|
+
async function policyStamping(databaseUrl, collectionsPath, policiesSql) {
|
|
1754
|
+
try {
|
|
1755
|
+
const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
|
|
1756
|
+
const { planCollectionPolicies } = await import("./generate-postgres-ddl-logic-B64O2oZF.js").then((n) => n.o);
|
|
1757
|
+
const { policyStampSql } = await import("./ensure-collection-policies-CXpbeNag.js");
|
|
1758
|
+
const { pinSearchPath } = await import("./connection-C9mT8bgf.js").then((n) => n.n);
|
|
1759
|
+
return {
|
|
1760
|
+
statements: stampableTables(planCollectionPolicies(await loadCollections(path.resolve(process.cwd(), collectionsPath))), policiesSql).map((plan) => policyStampSql(plan)).filter((statement) => statement !== void 0),
|
|
1761
|
+
searchPath: pinSearchPath(databaseUrl) === databaseUrl ? void 0 : "public"
|
|
1762
|
+
};
|
|
1763
|
+
} catch {
|
|
1764
|
+
return { statements: [] };
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
/**
|
|
1412
1768
|
* Remove the policies an earlier push superseded but never dropped.
|
|
1413
1769
|
*
|
|
1414
1770
|
* `policies.sql` only DROPs the names it is about to CREATE, and a rule's
|
|
@@ -1423,13 +1779,10 @@ async function applyPolicies(databaseUrl) {
|
|
|
1423
1779
|
*/
|
|
1424
1780
|
async function reconcilePolicies(databaseUrl, collectionsPath) {
|
|
1425
1781
|
try {
|
|
1426
|
-
const { checkPolicyDrift, dropOrphanedPolicies, formatPolicyDrift, hasDrift } = await import("./policy-drift-
|
|
1427
|
-
const { loadCollections } = await import("./doctor-
|
|
1782
|
+
const { checkPolicyDrift, dropOrphanedPolicies, formatPolicyDrift, hasDrift } = await import("./policy-drift-Dm8i-s1J.js").then((n) => n.a);
|
|
1783
|
+
const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
|
|
1428
1784
|
const collections = await loadCollections(path.resolve(process.cwd(), collectionsPath));
|
|
1429
|
-
|
|
1430
|
-
const client = new Client({ connectionString: databaseUrl });
|
|
1431
|
-
await client.connect();
|
|
1432
|
-
try {
|
|
1785
|
+
await withAppClient(databaseUrl, async (client) => {
|
|
1433
1786
|
const drift = await checkPolicyDrift(client, collections);
|
|
1434
1787
|
const { dropped, kept } = await dropOrphanedPolicies(client, drift, collections);
|
|
1435
1788
|
for (const p of dropped) out(chalk.gray(` ✓ Dropped superseded policy "${p.name}" on ${p.schema}.${p.table}`));
|
|
@@ -1450,9 +1803,7 @@ async function reconcilePolicies(databaseUrl, collectionsPath) {
|
|
|
1450
1803
|
orphaned: []
|
|
1451
1804
|
}));
|
|
1452
1805
|
}
|
|
1453
|
-
}
|
|
1454
|
-
await client.end();
|
|
1455
|
-
}
|
|
1806
|
+
});
|
|
1456
1807
|
} catch (err) {
|
|
1457
1808
|
outWarn(chalk.yellow(` ⚠️ Could not reconcile RLS policies: ${err instanceof Error ? err.message : String(err)}`));
|
|
1458
1809
|
}
|
|
@@ -1505,8 +1856,8 @@ async function branchCommand(rawArgs) {
|
|
|
1505
1856
|
outError(chalk.red("✗ DATABASE_URL is not set. Make sure your .env file is configured."));
|
|
1506
1857
|
process.exit(1);
|
|
1507
1858
|
}
|
|
1508
|
-
const { DatabasePoolManager } = await import("./databasePoolManager-
|
|
1509
|
-
const { BranchService } = await import("./BranchService-
|
|
1859
|
+
const { DatabasePoolManager } = await import("./databasePoolManager-BIuewidb.js").then((n) => n.n);
|
|
1860
|
+
const { BranchService } = await import("./BranchService-Bsn2wfaj.js").then((n) => n.n);
|
|
1510
1861
|
const { drizzle } = await import("drizzle-orm/node-postgres");
|
|
1511
1862
|
const { Pool } = await import("pg");
|
|
1512
1863
|
const pool = new Pool({
|
|
@@ -1795,8 +2146,15 @@ async function runAtlas(domain, args, collectionsPath, opts = {}) {
|
|
|
1795
2146
|
process.exit(1);
|
|
1796
2147
|
}
|
|
1797
2148
|
await checkDatabaseConnectivity(databaseUrl);
|
|
1798
|
-
|
|
1799
|
-
|
|
2149
|
+
let devDatabase;
|
|
2150
|
+
try {
|
|
2151
|
+
devDatabase = atlasDevDatabaseUrl(databaseUrl, env);
|
|
2152
|
+
} catch (err) {
|
|
2153
|
+
outError(chalk.red(`✗ ${err instanceof Error ? err.message : String(err)}`));
|
|
2154
|
+
process.exit(1);
|
|
2155
|
+
}
|
|
2156
|
+
const devDatabaseUrl = devDatabase.url;
|
|
2157
|
+
if (takesDevUrl(domain, args) && !devDatabase.named) await scratchDatabase.ensure(databaseUrl, devDatabaseUrl);
|
|
1800
2158
|
const atlasUrl = forLibpq(databaseUrl);
|
|
1801
2159
|
const atlasDevUrl = forLibpq(devDatabaseUrl);
|
|
1802
2160
|
const excludes = [];
|
|
@@ -1871,7 +2229,7 @@ async function runAtlas(domain, args, collectionsPath, opts = {}) {
|
|
|
1871
2229
|
if (opts.captureStdout) return stdoutText.trim().length > 0 ? stdoutText : stderrText;
|
|
1872
2230
|
return "";
|
|
1873
2231
|
}
|
|
1874
|
-
async function generatePostgresDdlCommand(rawArgs) {
|
|
2232
|
+
async function generatePostgresDdlCommand(rawArgs, options = {}) {
|
|
1875
2233
|
const argsList = (0, import_arg.default)({
|
|
1876
2234
|
"--collections": String,
|
|
1877
2235
|
"--output": String,
|
|
@@ -1881,20 +2239,31 @@ async function generatePostgresDdlCommand(rawArgs) {
|
|
|
1881
2239
|
argv: rawArgs.slice(2),
|
|
1882
2240
|
permissive: true
|
|
1883
2241
|
});
|
|
1884
|
-
const ddl = resolveChildScript("generate-postgres-ddl");
|
|
1885
|
-
if (!ddl.ok) {
|
|
2242
|
+
const ddl = generatesInProcess() ? void 0 : resolveChildScript("generate-postgres-ddl");
|
|
2243
|
+
if (ddl && !ddl.ok) {
|
|
1886
2244
|
outError(chalk.red(childScriptError("DDL generator", ddl.reason)));
|
|
1887
2245
|
process.exit(1);
|
|
1888
2246
|
}
|
|
1889
2247
|
const collectionsPath = argsList["--collections"] || path.join("..", "config", "collections");
|
|
1890
2248
|
const outputPath = argsList["--output"] || path.join(".rebase/sql", "schema.sql");
|
|
1891
2249
|
if (!argsList["--output"]) {
|
|
1892
|
-
const
|
|
1893
|
-
|
|
2250
|
+
const retire = options.retireLegacyCopies !== false;
|
|
2251
|
+
const retired = prepareGeneratedSqlDir(process.cwd(), { retire });
|
|
2252
|
+
if (retired.length > 0 && !retire) out(chalk.gray(` The next \`rebase db push\` or \`rebase db generate\` removes the old copies an earlier release committed:\n ${retired.join(", ")}`));
|
|
2253
|
+
else if (retired.length > 0) out(chalk.gray(` Generated SQL is written to ${GENERATED_SQL_DIR}/ now, which is not committed. Removed the old copies — commit the deletion:\n ${retired.join(", ")}`));
|
|
2254
|
+
}
|
|
2255
|
+
if (!ddl) {
|
|
2256
|
+
await generateInProcess("Postgres DDL generator", "Postgres DDL", async ({ writePostgresDdl }) => {
|
|
2257
|
+
await writePostgresDdl({
|
|
2258
|
+
collectionsPath: path.resolve(process.cwd(), collectionsPath),
|
|
2259
|
+
outputPath: path.resolve(process.cwd(), outputPath)
|
|
2260
|
+
});
|
|
2261
|
+
});
|
|
2262
|
+
return;
|
|
1894
2263
|
}
|
|
1895
2264
|
const cmdParts = [
|
|
1896
2265
|
ddl.bin,
|
|
1897
|
-
ddl.
|
|
2266
|
+
...ddl.args,
|
|
1898
2267
|
`--collections=${collectionsPath}`,
|
|
1899
2268
|
`--output=${outputPath}`
|
|
1900
2269
|
];
|
|
@@ -1943,9 +2312,9 @@ async function schemaStaleCommand(rawArgs) {
|
|
|
1943
2312
|
const schemaFile = path.resolve(process.cwd(), outputPath);
|
|
1944
2313
|
const fix = Boolean(argsList["--fix"]);
|
|
1945
2314
|
const generatedExists = fs.existsSync(schemaFile);
|
|
1946
|
-
const { loadCollections } = await import("./doctor-
|
|
2315
|
+
const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
|
|
1947
2316
|
const { findLegacyForeignKeyNames, compareGeneratedDeclarations, staleVerdict } = await import("./generated-schema-staleness-Di9c4ZxN.js").then((n) => n.r);
|
|
1948
|
-
const { generateSchema } = await import("./generate-drizzle-schema-logic-
|
|
2317
|
+
const { generateSchema } = await import("./generate-drizzle-schema-logic-B74WN37S.js").then((n) => n.n);
|
|
1949
2318
|
const { relationalCollections } = await import("@rebasepro/common");
|
|
1950
2319
|
let stale = [];
|
|
1951
2320
|
let differences = [];
|
|
@@ -1958,6 +2327,7 @@ async function schemaStaleCommand(rawArgs) {
|
|
|
1958
2327
|
if (postgresCollections.length > 0) differences = compareGeneratedDeclarations(generateSchema(postgresCollections), onDisk);
|
|
1959
2328
|
} catch (err) {
|
|
1960
2329
|
unreadable = err instanceof Error ? err.message : String(err);
|
|
2330
|
+
const { logger } = await import("@rebasepro/server");
|
|
1961
2331
|
logger.debug(`schema stale: skipped (${unreadable})`);
|
|
1962
2332
|
}
|
|
1963
2333
|
const verdict = staleVerdict({
|
|
@@ -1982,7 +2352,7 @@ async function schemaStaleCommand(rawArgs) {
|
|
|
1982
2352
|
`--output=${outputPath}`
|
|
1983
2353
|
]);
|
|
1984
2354
|
}
|
|
1985
|
-
async function schemaCommand(subcommand, rawArgs) {
|
|
2355
|
+
async function schemaCommand(subcommand, rawArgs, options = {}) {
|
|
1986
2356
|
if (subcommand && (rawArgs.includes("--help") || rawArgs.includes("-h"))) {
|
|
1987
2357
|
out("");
|
|
1988
2358
|
out(chalk.bold(` rebase schema ${subcommand}`));
|
|
@@ -2007,24 +2377,38 @@ async function schemaCommand(subcommand, rawArgs) {
|
|
|
2007
2377
|
argv: rawArgs.slice(2),
|
|
2008
2378
|
permissive: true
|
|
2009
2379
|
});
|
|
2380
|
+
const collectionsPath = argsList["--collections"] || path.join("..", "config", "collections");
|
|
2381
|
+
const outputPath = argsList["--output"] || path.join("src", "schema.generated.ts");
|
|
2382
|
+
const watch = argsList["--watch"] || false;
|
|
2383
|
+
if (!watch && generatesInProcess()) {
|
|
2384
|
+
out("");
|
|
2385
|
+
out(chalk.bold(" 🔧 Rebase Schema Generator"));
|
|
2386
|
+
out("");
|
|
2387
|
+
await generateInProcess("schema generator", "Drizzle schema", async ({ writeDrizzleSchema }) => {
|
|
2388
|
+
await writeDrizzleSchema({
|
|
2389
|
+
collectionsPath: path.resolve(process.cwd(), collectionsPath),
|
|
2390
|
+
outputPath: path.resolve(process.cwd(), outputPath),
|
|
2391
|
+
nextStep: options.nextStep
|
|
2392
|
+
});
|
|
2393
|
+
});
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2010
2396
|
const generator = resolveChildScript("generate-drizzle-schema");
|
|
2011
2397
|
if (!generator.ok) {
|
|
2012
2398
|
outError(chalk.red(childScriptError("schema generator", generator.reason)));
|
|
2013
2399
|
process.exit(1);
|
|
2014
2400
|
}
|
|
2015
|
-
const collectionsPath = argsList["--collections"] || path.join("..", "config", "collections");
|
|
2016
|
-
const outputPath = argsList["--output"] || path.join("src", "schema.generated.ts");
|
|
2017
|
-
const watch = argsList["--watch"] || false;
|
|
2018
2401
|
out("");
|
|
2019
2402
|
out(chalk.bold(" 🔧 Rebase Schema Generator"));
|
|
2020
2403
|
out("");
|
|
2021
2404
|
const cmdParts = [
|
|
2022
2405
|
generator.bin,
|
|
2023
|
-
generator.
|
|
2406
|
+
...generator.args,
|
|
2024
2407
|
`--collections=${collectionsPath}`,
|
|
2025
2408
|
`--output=${outputPath}`
|
|
2026
2409
|
];
|
|
2027
2410
|
if (watch) cmdParts.push("--watch");
|
|
2411
|
+
if (options.nextStep === false) cmdParts.push("--no-next-step");
|
|
2028
2412
|
try {
|
|
2029
2413
|
await execa(cmdParts[0], cmdParts.slice(1), {
|
|
2030
2414
|
cwd: process.cwd(),
|
|
@@ -2061,7 +2445,7 @@ async function schemaCommand(subcommand, rawArgs) {
|
|
|
2061
2445
|
out("");
|
|
2062
2446
|
const cmdParts = [
|
|
2063
2447
|
introspect.bin,
|
|
2064
|
-
introspect.
|
|
2448
|
+
...introspect.args,
|
|
2065
2449
|
`--output=${outputPath}`,
|
|
2066
2450
|
...argsList["--force"] ? ["--force"] : [],
|
|
2067
2451
|
...argsList["--schema"] ? [`--schema=${argsList["--schema"]}`] : []
|
|
@@ -2104,7 +2488,7 @@ async function doctorPluginCommand(rawArgs) {
|
|
|
2104
2488
|
const sdkPath = parsedArgs["--sdk"] || path.join("..", "generated", "sdk", "database.types.ts");
|
|
2105
2489
|
const cmdParts = [
|
|
2106
2490
|
doctor.bin,
|
|
2107
|
-
doctor.
|
|
2491
|
+
...doctor.args,
|
|
2108
2492
|
`--collections=${collectionsPath}`,
|
|
2109
2493
|
`--schema=${schemaPath}`,
|
|
2110
2494
|
`--sdk=${sdkPath}`,
|