@rebasepro/server-postgres 0.21.2-canary.g1ea48be → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/{BranchService-CucnFcSE.js → BranchService-DGPL6G_C.js} +2 -2
  2. package/dist/{BranchService-CucnFcSE.js.map → BranchService-DGPL6G_C.js.map} +1 -1
  3. package/dist/PostgresBackendDriver.d.ts +41 -1
  4. package/dist/auth/services.d.ts +27 -8
  5. package/dist/backup/backup-cli.d.ts +22 -0
  6. package/dist/{backup-cli-CkjsJEcu.js → backup-cli-24v4OlSp.js} +17 -14
  7. package/dist/backup-cli-24v4OlSp.js.map +1 -0
  8. package/dist/{backup-service-BNLwvxuy.js → backup-service-HQ9GC4tN.js} +2 -2
  9. package/dist/{backup-service-BNLwvxuy.js.map → backup-service-HQ9GC4tN.js.map} +1 -1
  10. package/dist/{cli-errors-Dka89exj.js → cli-errors-B8qHg02P.js} +6 -1
  11. package/dist/cli-errors-B8qHg02P.js.map +1 -0
  12. package/dist/cli-flags-BglvpjHv.js +138 -0
  13. package/dist/cli-flags-BglvpjHv.js.map +1 -0
  14. package/dist/cli-flags.d.ts +28 -0
  15. package/dist/cli-helpers.d.ts +6 -0
  16. package/dist/cli-scratch-database.d.ts +34 -0
  17. package/dist/cli.js +97 -261
  18. package/dist/cli.js.map +1 -1
  19. package/dist/{column-plan-helpers-CpILzHJS.js → column-plan-helpers-DF-8dTVa.js} +16 -20
  20. package/dist/column-plan-helpers-DF-8dTVa.js.map +1 -0
  21. package/dist/{doctor-Gzzd8wTq.js → doctor-Cb2thZ8s.js} +8 -7
  22. package/dist/doctor-Cb2thZ8s.js.map +1 -0
  23. package/dist/{ensure-collection-policies-B4IDFtQ-.js → ensure-collection-policies-RHUcEp4v.js} +5 -5
  24. package/dist/{ensure-collection-policies-B4IDFtQ-.js.map → ensure-collection-policies-RHUcEp4v.js.map} +1 -1
  25. package/dist/{ensure-collection-tables-Cr2ye5JC.js → ensure-collection-tables-BEEjn5cn.js} +53 -22
  26. package/dist/ensure-collection-tables-BEEjn5cn.js.map +1 -0
  27. package/dist/{ensure-tables-BnEvEJPr.js → ensure-tables-Dhn9KM3B.js} +2 -2
  28. package/dist/{ensure-tables-BnEvEJPr.js.map → ensure-tables-Dhn9KM3B.js.map} +1 -1
  29. package/dist/{generate-drizzle-schema-logic-DFa9qy9u.js → generate-drizzle-schema-logic-BfzK7UQd.js} +2 -2
  30. package/dist/{generate-drizzle-schema-logic-DFa9qy9u.js.map → generate-drizzle-schema-logic-BfzK7UQd.js.map} +1 -1
  31. package/dist/{generate-drizzle-schema-D2MDdJt-.js → generate-drizzle-schema-yzY_BLhr.js} +2 -2
  32. package/dist/{generate-drizzle-schema-D2MDdJt-.js.map → generate-drizzle-schema-yzY_BLhr.js.map} +1 -1
  33. package/dist/{generate-postgres-ddl-logic-CR2xcS7e.js → generate-postgres-ddl-logic-Bt2d2mRH.js} +2 -2
  34. package/dist/{generate-postgres-ddl-logic-CR2xcS7e.js.map → generate-postgres-ddl-logic-Bt2d2mRH.js.map} +1 -1
  35. package/dist/index.es.js +1882 -392
  36. package/dist/index.es.js.map +1 -1
  37. package/dist/{introspect-db-logic-C6LQdTxj.js → introspect-db-logic-WMuAfxvw.js} +323 -4
  38. package/dist/introspect-db-logic-WMuAfxvw.js.map +1 -0
  39. package/dist/{plan-schema-Hgl62S-w.js → plan-schema-C0fxM8dY.js} +170 -38
  40. package/dist/plan-schema-C0fxM8dY.js.map +1 -0
  41. package/dist/{policy-drift-B0GDRh7_.js → policy-drift-DljYdrpW.js} +2 -2
  42. package/dist/{policy-drift-B0GDRh7_.js.map → policy-drift-DljYdrpW.js.map} +1 -1
  43. package/dist/{rls-bootstrap-sql-BHA6jLtj.js → rls-bootstrap-sql-B8EclDyM.js} +320 -17
  44. package/dist/rls-bootstrap-sql-B8EclDyM.js.map +1 -0
  45. package/dist/schema/atlas-argv.d.ts +15 -0
  46. package/dist/schema/column-plan-helpers.d.ts +23 -4
  47. package/dist/schema/destructive-sql.d.ts +52 -1
  48. package/dist/schema/doctor-cli.js +3 -3
  49. package/dist/schema/generate-drizzle-schema.js +1 -1
  50. package/dist/schema/generate-postgres-ddl.js +1 -1
  51. package/dist/schema/introspect-db.js +1 -312
  52. package/dist/schema/introspect-db.js.map +1 -1
  53. package/dist/schema/plan/diff-plan.d.ts +3 -2
  54. package/dist/schema/plan/plan-schema.d.ts +10 -3
  55. package/dist/schema/plan/types.d.ts +6 -1
  56. package/dist/schema/search-column.d.ts +21 -1
  57. package/dist/services/FetchService.d.ts +147 -30
  58. package/dist/services/PersistService.d.ts +56 -5
  59. package/dist/services/RelationService.d.ts +42 -1
  60. package/dist/services/RelationWriteService.d.ts +22 -1
  61. package/dist/services/cdc/CdcListener.d.ts +6 -1
  62. package/dist/services/collection-helpers.d.ts +11 -0
  63. package/dist/services/dataService.d.ts +19 -21
  64. package/dist/services/field-op-sql.d.ts +71 -0
  65. package/dist/services/junction-writes.d.ts +19 -3
  66. package/dist/services/pg-notify-listener.d.ts +7 -0
  67. package/dist/services/read-field-access.d.ts +19 -0
  68. package/dist/services/read-scope.d.ts +100 -0
  69. package/dist/services/realtimeService.d.ts +77 -3
  70. package/dist/services/row-pipeline.d.ts +5 -0
  71. package/dist/services/soft-delete.d.ts +0 -2
  72. package/dist/services/write-transaction-scope.d.ts +39 -0
  73. package/dist/utils/drizzle-conditions.d.ts +63 -8
  74. package/dist/websocket.d.ts +10 -3
  75. package/package.json +9 -9
  76. package/dist/backup-cli-CkjsJEcu.js.map +0 -1
  77. package/dist/cli-errors-Dka89exj.js.map +0 -1
  78. package/dist/column-plan-helpers-CpILzHJS.js.map +0 -1
  79. package/dist/doctor-Gzzd8wTq.js.map +0 -1
  80. package/dist/ensure-collection-tables-Cr2ye5JC.js.map +0 -1
  81. package/dist/introspect-db-logic-C6LQdTxj.js.map +0 -1
  82. package/dist/plan-schema-Hgl62S-w.js.map +0 -1
  83. package/dist/rls-bootstrap-sql-BHA6jLtj.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-flags-BglvpjHv.js","names":[],"sources":["../src/cli-flags.ts"],"sourcesContent":["/**\n * The flags each driver command takes, in one place, and the check that\n * enforces them.\n *\n * Every parser in `cli.ts` runs `arg(..., { permissive: true })`, which does not\n * mean \"be lenient\" — it means **an undeclared flag becomes a positional**. So\n * `rebase db push --alow-destructive` did not fail: the typo landed in `_`, the\n * push ran with the destructive gate still closed, and the developer read the\n * refusal as Rebase ignoring the flag they had just typed. `rebase schema\n * generate --ouput src/schema.ts` was worse — it wrote the default path and\n * said nothing, so the next build compiled a file nobody had regenerated.\n *\n * The check has to live at the entry point rather than in those parsers, and\n * that is not a detail: `db push` and `db generate` re-enter `schemaCommand`\n * and `generatePostgresDdlCommand` with the *db* line, so a strict parser\n * inside either of them would reject `--allow-destructive` on a line where it\n * is correct. One validation, once, against the spec for the command the user\n * actually named; the inner parsers keep reading an already-validated line.\n *\n * Commands with their own argument handling — `db branch` (`unexpectedBranchArgs`),\n * `db backup`, `db restore`, `db backups` — are deliberately absent: they own\n * their spec, and a second list here would be one more thing to keep in step.\n * An absent entry is \"not checked here\", never \"takes nothing\".\n */\nimport arg from \"arg\";\n\n/**\n * Flags that reach this process on a line it did not compose.\n *\n * `rebase db …` hands the driver the user's whole line: the CLI *reads*\n * `--database-url` and `--docker` to resolve the database and then relays them\n * verbatim, and `--debug` is what `bin/rebase.js` prints after every failure as\n * the thing to re-run with. Rejecting any of the three would make the driver\n * refuse commands the CLI documents.\n */\nexport const RELAYED_FLAGS = {\n \"--database-url\": String,\n \"--docker\": Boolean,\n \"--debug\": Boolean,\n \"--help\": Boolean,\n \"-h\": \"--help\"\n} satisfies arg.Spec;\n\n/**\n * Parse a driver command's line — `[\"db\", \"<subcommand>\", …]` — with the\n * relayed flags declared alongside the command's own.\n *\n * Every driver parser is permissive, and an undeclared flag in a permissive\n * parse is a positional. The positionals are what `db migrate` hands\n * `atlas migrate apply`, what `db generate` names its migration after and\n * which backup `db restore` restores, so a relayed flag left undeclared was\n * read as one of those: `rebase db migrate --docker` died on Atlas's\n * `unknown flag: --docker` after it had already written the auth schema.\n */\nexport function parseDriverLine<S extends arg.Spec>(\n spec: S,\n args: string[]\n): arg.Result<typeof RELAYED_FLAGS & S> {\n return arg({ ...RELAYED_FLAGS, ...spec }, { argv: args.slice(2), permissive: true });\n}\n\n/** Keyed by `\"<domain> <subcommand>\"`, the way the user types it. */\nexport const DRIVER_FLAG_SPECS: Record<string, arg.Spec> = {\n \"db push\": {\n \"--collections\": String,\n \"-c\": \"--collections\",\n \"--dry-run\": Boolean,\n \"--allow-destructive\": Boolean,\n \"--yes\": Boolean,\n \"-y\": \"--yes\"\n },\n \"db generate\": {\n \"--collections\": String,\n \"-c\": \"--collections\"\n },\n // Bare positionals still pass through to `atlas migrate apply`, which takes\n // an optional amount; flags do not, apart from `--baseline`.\n //\n // `--baseline <version>` is the escape hatch for the normal case, not an\n // exotic one: every Rebase boot ensures the schema, so a production\n // database has the tables before its first migration ever runs, and\n // `migrate apply` then dies on `type \"…\" already exists (42710)`. It maps\n // onto Atlas's own `migrate apply --baseline`, which writes the revision\n // row Atlas reads — no ledger of ours.\n \"db migrate\": {\n \"--baseline\": String\n },\n \"schema generate\": {\n \"--collections\": String,\n \"-c\": \"--collections\",\n \"--output\": String,\n \"-o\": \"--output\",\n \"--out\": \"--output\",\n \"--watch\": Boolean,\n \"-w\": \"--watch\"\n },\n \"schema introspect\": {\n \"--output\": String,\n \"-o\": \"--output\",\n \"--out\": \"--output\",\n \"--collections\": String,\n \"-c\": \"--collections\",\n \"--force\": Boolean,\n \"-f\": \"--force\",\n \"--schema\": String\n },\n \"schema stale\": {\n \"--collections\": String,\n \"-c\": \"--collections\",\n \"--output\": String,\n \"-o\": \"--output\",\n \"--out\": \"--output\",\n \"--fix\": Boolean\n }\n};\n\n/**\n * One destination flag, two spellings, everywhere.\n *\n * `--out` is the primary on `rebase build`, an alias on `generate-sdk`, `db\n * backup` and `cloud env pull`, and was refused outright by the three `schema`\n * commands — so the spelling a user learned on one command was an\n * \"unknown or unexpected option\" on the next. Neither name can be retired (both\n * are shipped), so both are accepted, and {@link assertOutputAliasesPaired}\n * makes that the rule rather than a habit.\n */\nexport const OUTPUT_FLAG_ALIASES = [\"--out\", \"--output\"] as const;\n\n/**\n * Every spec that names one of the pair names both.\n *\n * Exported so the CLI's own specs can be held to it too: the drift this fixes\n * ran across two packages, and a check that only reads this file would let the\n * next `--out`-only command through.\n */\nexport function assertOutputAliasesPaired(specs: Record<string, arg.Spec>): string[] {\n const problems: string[] = [];\n for (const [command, spec] of Object.entries(specs)) {\n const present = OUTPUT_FLAG_ALIASES.filter(flag => flag in spec);\n if (present.length === 0 || present.length === OUTPUT_FLAG_ALIASES.length) continue;\n const missing = OUTPUT_FLAG_ALIASES.filter(flag => !(flag in spec));\n problems.push(`\\`${command}\\` takes ${present.join(\", \")} but not ${missing.join(\", \")}`);\n }\n return problems;\n}\n\n/**\n * The `--collections` path the user typed, or `null` if they typed none.\n *\n * Read once, at the entry point, because every command that takes it re-enters\n * the generators with the same line and each of them resolved it again — which\n * is how \"Collections path not found\" came to be printed four times before the\n * real output, and how a path that does not exist got as far as *writing* an\n * empty schema.\n *\n * Permissive, and it has to be: {@link assertKnownFlags} has already judged the\n * line, and `db push` carries flags the `--collections` spec does not name.\n */\nexport function collectionsPathIn(args: string[]): string | null {\n try {\n const parsed = arg(\n { \"--collections\": String, \"-c\": \"--collections\" },\n { argv: args.slice(2), permissive: true }\n );\n\n return parsed[\"--collections\"] ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * The long flags a `--help` usage line documents.\n *\n * `\"rebase db push [--collections <dir>] [--dry-run] …\"` → `[\"--collections\",\n * \"--dry-run\"]`. Placeholders (`<dir>`) and the alternation inside a positional\n * (`<create|list|switch>`) are not flags and are not returned.\n */\nexport function flagsInUsage(usage: string): string[] {\n return [...new Set(usage.match(/--[a-z][a-z0-9-]*/g) ?? [])];\n}\n\n/**\n * Every flag the help documents is accepted, and every flag accepted is documented.\n *\n * The drift this catches shipped: `rebase db push --help` has printed\n * `[--dry-run]` since the flag was written, `DRIVER_FLAG_SPECS[\"db push\"]` never\n * listed it, and {@link assertKnownFlags} — added later to stop typos being\n * swallowed — turned the documented flag into `unknown or unexpected option`.\n * The only way to see a push's SQL was to trip the destructive gate, which is\n * the exact problem `--dry-run` was written to solve. Worse in a project on an\n * older driver, whose permissive parser *applied* the schema on that line.\n *\n * Two hand-maintained lists in two packages cannot be kept in step by care, so\n * they are held to each other instead: `usages` is keyed the way\n * {@link DRIVER_FLAG_SPECS} is (`\"db push\"`), and comes from the CLI's own help\n * pages. A key in only one of the two is not this function's business — the\n * help covers commands that parse their own lines (`db branch`, `db backup`),\n * and an absent spec entry means \"not checked here\".\n *\n * Aliases (`\"-c\": \"--collections\"`, `\"--out\": \"--output\"`) need no line of their\n * own: they are spellings of a documented flag. Neither do {@link RELAYED_FLAGS},\n * which every driver command accepts because the CLI relays them.\n */\nexport function assertSpecMatchesUsage(\n specs: Record<string, arg.Spec>,\n usages: Record<string, string>\n): string[] {\n const problems: string[] = [];\n for (const [command, spec] of Object.entries(specs)) {\n const usage = usages[command];\n if (usage === undefined) continue;\n\n const documented = flagsInUsage(usage);\n const accepted = Object.entries(spec);\n // An alias resolves to the flag it spells; only the target needs a line.\n const canonical = new Set(\n accepted\n .map(([flag, kind]) => (typeof kind === \"string\" ? kind : flag))\n .filter(flag => flag.startsWith(\"--\"))\n );\n\n for (const flag of documented) {\n if (flag in spec || flag in RELAYED_FLAGS) continue;\n problems.push(`\\`${command}\\` documents ${flag} in its usage line but the spec rejects it`);\n }\n for (const flag of canonical) {\n if (documented.includes(flag) || flag in RELAYED_FLAGS) continue;\n problems.push(`\\`${command}\\` accepts ${flag} but no usage line documents it`);\n }\n }\n return problems;\n}\n\n/**\n * Reject a flag the named command does not take.\n *\n * `args` is the driver's whole line — `[\"db\", \"push\", …]` — so the flags are\n * read from `args.slice(2)`, past the two command words.\n *\n * Throws rather than exiting: `runPluginCommand`'s caller already turns a\n * thrown error into one red line and exit 1, and a thrown error is what the\n * tests can see.\n */\nexport function assertKnownFlags(domain: string, subcommand: string | undefined, args: string[]): void {\n if (!subcommand) return;\n const spec = DRIVER_FLAG_SPECS[`${domain} ${subcommand}`];\n if (!spec) return;\n\n try {\n arg({ ...RELAYED_FLAGS, ...spec }, { argv: args.slice(2) });\n } catch (err) {\n if (err instanceof Error && (err as arg.ArgError).code === \"ARG_UNKNOWN_OPTION\") {\n throw new Error(\n `${err.message} — run \\`rebase ${domain} --help\\` for the options ` +\n `\\`${domain} ${subcommand}\\` takes.`\n );\n }\n throw err;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAmCA,IAAa,gBAAgB;CACzB,kBAAkB;CAClB,YAAY;CACZ,WAAW;CACX,UAAU;CACV,MAAM;AACV;;;;;;;;;;;;AAaA,SAAgB,gBACZ,MACA,MACoC;CACpC,QAAA,GAAA,WAAA,QAAA,CAAW;EAAE,GAAG;EAAe,GAAG;CAAK,GAAG;EAAE,MAAM,KAAK,MAAM,CAAC;EAAG,YAAY;CAAK,CAAC;AACvF;;AAGA,IAAa,oBAA8C;CACvD,WAAW;EACP,iBAAiB;EACjB,MAAM;EACN,aAAa;EACb,uBAAuB;EACvB,SAAS;EACT,MAAM;CACV;CACA,eAAe;EACX,iBAAiB;EACjB,MAAM;CACV;CAUA,cAAc,EACV,cAAc,OAClB;CACA,mBAAmB;EACf,iBAAiB;EACjB,MAAM;EACN,YAAY;EACZ,MAAM;EACN,SAAS;EACT,WAAW;EACX,MAAM;CACV;CACA,qBAAqB;EACjB,YAAY;EACZ,MAAM;EACN,SAAS;EACT,iBAAiB;EACjB,MAAM;EACN,WAAW;EACX,MAAM;EACN,YAAY;CAChB;CACA,gBAAgB;EACZ,iBAAiB;EACjB,MAAM;EACN,YAAY;EACZ,MAAM;EACN,SAAS;EACT,SAAS;CACb;AACJ;;;;;;;;;;;;;AA4CA,SAAgB,kBAAkB,MAA+B;CAC7D,IAAI;EAMA,QAAA,GAAA,WAAA,QAAA,CAJI;GAAE,iBAAiB;GAAQ,MAAM;EAAgB,GACjD;GAAE,MAAM,KAAK,MAAM,CAAC;GAAG,YAAY;EAAK,CAGrC,CAAA,CAAO,oBAAoB;CACtC,QAAQ;EACJ,OAAO;CACX;AACJ;;;;;;;;;;;AA2EA,SAAgB,iBAAiB,QAAgB,YAAgC,MAAsB;CACnG,IAAI,CAAC,YAAY;CACjB,MAAM,OAAO,kBAAkB,GAAG,OAAO,GAAG;CAC5C,IAAI,CAAC,MAAM;CAEX,IAAI;EACA,CAAA,GAAA,WAAA,QAAA,CAAI;GAAE,GAAG;GAAe,GAAG;EAAK,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,EAAE,CAAC;CAC9D,SAAS,KAAK;EACV,IAAI,eAAe,SAAU,IAAqB,SAAS,sBACvD,MAAM,IAAI,MACN,GAAG,IAAI,QAAQ,kBAAkB,OAAO,8BACnC,OAAO,GAAG,WAAW,UAC9B;EAEJ,MAAM;CACV;AACJ"}
@@ -23,6 +23,34 @@
23
23
  * An absent entry is "not checked here", never "takes nothing".
24
24
  */
25
25
  import arg from "arg";
26
+ /**
27
+ * Flags that reach this process on a line it did not compose.
28
+ *
29
+ * `rebase db …` hands the driver the user's whole line: the CLI *reads*
30
+ * `--database-url` and `--docker` to resolve the database and then relays them
31
+ * verbatim, and `--debug` is what `bin/rebase.js` prints after every failure as
32
+ * the thing to re-run with. Rejecting any of the three would make the driver
33
+ * refuse commands the CLI documents.
34
+ */
35
+ export declare const RELAYED_FLAGS: {
36
+ "--database-url": StringConstructor;
37
+ "--docker": BooleanConstructor;
38
+ "--debug": BooleanConstructor;
39
+ "--help": BooleanConstructor;
40
+ "-h": string;
41
+ };
42
+ /**
43
+ * Parse a driver command's line — `["db", "<subcommand>", …]` — with the
44
+ * relayed flags declared alongside the command's own.
45
+ *
46
+ * Every driver parser is permissive, and an undeclared flag in a permissive
47
+ * parse is a positional. The positionals are what `db migrate` hands
48
+ * `atlas migrate apply`, what `db generate` names its migration after and
49
+ * which backup `db restore` restores, so a relayed flag left undeclared was
50
+ * read as one of those: `rebase db migrate --docker` died on Atlas's
51
+ * `unknown flag: --docker` after it had already written the auth schema.
52
+ */
53
+ export declare function parseDriverLine<S extends arg.Spec>(spec: S, args: string[]): arg.Result<typeof RELAYED_FLAGS & S>;
26
54
  /** Keyed by `"<domain> <subcommand>"`, the way the user types it. */
27
55
  export declare const DRIVER_FLAG_SPECS: Record<string, arg.Spec>;
28
56
  /**
@@ -1,4 +1,5 @@
1
1
  import type { GeneratedColumnDependency } from "./schema/generated-column-conflicts.js";
2
+ import { type ExistingColumnType } from "./schema/destructive-sql.js";
2
3
  import type { CollectionConfig } from "@rebasepro/types";
3
4
  /**
4
5
  * Why is a dependency's binary missing — never installed, or installed with its
@@ -164,6 +165,11 @@ export declare function getSearchExcludes(collectionsPath: string): Promise<stri
164
165
  * see `generated-column-conflicts.ts` for why that is necessary at all.
165
166
  */
166
167
  export declare function queryGeneratedColumnDependencies(databaseUrl: string): Promise<GeneratedColumnDependency[]>;
168
+ /**
169
+ * Every column in the database with its current type, for the destructive
170
+ * gate to tell a widening `ALTER COLUMN … TYPE` from one that loses data.
171
+ */
172
+ export declare function queryColumnTypes(databaseUrl: string): Promise<ExistingColumnType[]>;
167
173
  /**
168
174
  * Drop the generated columns standing in the way of an apply.
169
175
  *
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The scratch database Atlas plans against, for the length of one db command.
3
+ *
4
+ * `atlas schema apply` and `atlas migrate diff` need a `--dev-url`: an empty
5
+ * database they build the desired state in, made as `<db>_dev_diff` beside the
6
+ * real one. It is a full copy of the schema, so it is removed again once the
7
+ * command that made it has succeeded — every such command, not only an applied
8
+ * `db push`: a `--dry-run` that left one behind while saying nothing was
9
+ * applied, and `db generate`, left one per target on the server.
10
+ *
11
+ * Two cases keep it. A command that fails leaves it as the state Atlas was
12
+ * planning against, which is the one thing worth inspecting. And one this
13
+ * process did not create — made by hand, the remedy for a role without
14
+ * `CREATEDB` — is the user's, and dropping it would put them back at the same
15
+ * refusal next time.
16
+ */
17
+ export interface ScratchDatabaseOps {
18
+ /** Create it if it is missing; `true` only when this call created it. */
19
+ create(databaseUrl: string, devDatabaseUrl: string): Promise<boolean>;
20
+ drop(databaseUrl: string, devDatabaseUrl: string): Promise<void>;
21
+ }
22
+ export declare class ScratchDatabase {
23
+ private readonly ops;
24
+ private created;
25
+ constructor(ops: ScratchDatabaseOps);
26
+ /** Make sure it exists before an Atlas call that plans against it. */
27
+ ensure(databaseUrl: string, devDatabaseUrl: string): Promise<void>;
28
+ /**
29
+ * Run a command, then drop the scratch database if the command created it.
30
+ * A command that throws skips the drop; one that exits the process never
31
+ * returns here, which is the same thing.
32
+ */
33
+ around<T>(command: () => Promise<T>): Promise<T>;
34
+ }
package/dist/cli.js CHANGED
@@ -1,105 +1,19 @@
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-Dka89exj.js";
4
+ import { a as formatForeignGeneratedColumnBanner, c as reportCommandFailure, i as diagnoseDbError, r as diagnoseAtlasFailure, t as checkDatabaseConnectivity } from "./cli-errors-B8qHg02P.js";
5
5
  import { n as outError, r as outWarn, t as out } from "./cli-output-CNdMql-L.js";
6
- import { A as execa, C as loadCollectionsForCli, D as readTriggersDdl, E as readSearchDdl, O as readVectorDdl, S as getVectorExcludes, T as queryGeneratedColumnDependencies, _ as getDevDatabaseUrl, a as ExcludeIntrospectionError, b as getTableExcludes, c as applyVectorDdl, d as describeReinstallCommand, f as detectProjectPackageManager, g as ensureDevDatabaseExists, h as dropGeneratedColumns, j as forLibpq, k as resolveLocalBin, l as describeBuildScriptRemedy, m as dropDevDatabase, o as applySearchDdl, p as diagnoseMissingBin, r as dropLegacyAuthSchema, s as applyTriggersDdl, t as RLS_BOOTSTRAP_SQL, u as describeDevAddCommand, v as getForeignIndexExcludes, w as promptConfirm, x as getTriggerExcludes, y as getSearchExcludes } from "./rls-bootstrap-sql-BHA6jLtj.js";
6
+ import { A as resolveLocalBin, C as loadCollectionsForCli, D as readSearchDdl, E as queryGeneratedColumnDependencies, F as forLibpq, M as detectDestructiveStatements, N as splitSqlStatements, O as readTriggersDdl, P as execa, S as getVectorExcludes, T as queryColumnTypes, _ as getDevDatabaseUrl, a as ExcludeIntrospectionError, b as getTableExcludes, c as applyVectorDdl, d as describeReinstallCommand, f as detectProjectPackageManager, g as ensureDevDatabaseExists, h as dropGeneratedColumns, j as decidePushSafety, k as readVectorDdl, l as describeBuildScriptRemedy, m as dropDevDatabase, o as applySearchDdl, p as diagnoseMissingBin, r as dropLegacyAuthSchema, s as applyTriggersDdl, t as RLS_BOOTSTRAP_SQL, u as describeDevAddCommand, v as getForeignIndexExcludes, w as promptConfirm, x as getTriggerExcludes, y as getSearchExcludes } from "./rls-bootstrap-sql-B8EclDyM.js";
7
7
  import { t as chalk } from "./source-Br7L7GOI.js";
8
8
  import { t as require_arg } from "./arg-Dni7MzLB.js";
9
+ import { n as collectionsPathIn, r as parseDriverLine, t as assertKnownFlags } from "./cli-flags-BglvpjHv.js";
9
10
  import { logger } from "@rebasepro/server";
10
11
  import { formatRelativeTime } from "@rebasepro/utils";
11
12
  import fs from "fs";
12
13
  import path from "path";
13
14
  import { fileURLToPath } from "url";
14
- //#region src/schema/destructive-sql.ts
15
- var import_arg = /* @__PURE__ */ __toESM(require_arg(), 1);
16
- /**
17
- * Pure helpers that classify an Atlas declarative-apply plan as destructive
18
- * or not, and decide what `rebase db push` should do about it.
19
- *
20
- * `db push` runs `atlas schema apply` to make the live database match the
21
- * generated `schema.sql`. Removing a collection field compiles to
22
- * `DROP COLUMN`; renaming compiles to drop-then-add — either destroys data.
23
- * We first run the apply with `--dry-run` to obtain the planned SQL, scan it
24
- * here, and refuse to auto-approve anything destructive without an explicit
25
- * opt-in.
26
- *
27
- * Everything in this file is side-effect free so it can be unit-tested
28
- * without Atlas or a database.
29
- */
30
- /**
31
- * SQL fragments that destroy data or data-bearing objects. Matched
32
- * case-insensitively against each statement of the plan. `IF EXISTS` /
33
- * whitespace variations are tolerated by the regexes below.
34
- */
35
- var DESTRUCTIVE_PATTERNS = [
36
- {
37
- label: "DROP TABLE",
38
- re: /\bDROP\s+TABLE\b/i
39
- },
40
- {
41
- label: "DROP COLUMN",
42
- re: /\bDROP\s+COLUMN\b/i
43
- },
44
- {
45
- label: "DROP SCHEMA",
46
- re: /\bDROP\s+SCHEMA\b/i
47
- },
48
- {
49
- label: "DROP VIEW",
50
- re: /\bDROP\s+(MATERIALIZED\s+)?VIEW\b/i
51
- },
52
- {
53
- label: "DROP TYPE",
54
- re: /\bDROP\s+TYPE\b/i
55
- },
56
- {
57
- label: "TRUNCATE",
58
- re: /\bTRUNCATE\b/i
59
- }
60
- ];
61
- /**
62
- * Split a SQL script into individual statements, dropping blank lines and
63
- * `--` comment lines. Deliberately simple: Atlas emits one plain statement
64
- * per `;`, without string literals that contain semicolons in a schema DDL
65
- * plan, so a naive split is safe and keeps this dependency-free.
66
- */
67
- function splitSqlStatements(sql) {
68
- return sql.split("\n").filter((line) => !line.trim().startsWith("--")).join("\n").split(";").map((s) => s.trim()).filter((s) => s.length > 0);
69
- }
70
- /**
71
- * Scan an Atlas plan (the SQL printed by `schema apply --dry-run`) and return
72
- * the statements that would destroy data. An empty array means the plan is
73
- * safe to auto-approve.
74
- */
75
- function detectDestructiveStatements(planSql) {
76
- const found = [];
77
- for (const statement of splitSqlStatements(planSql)) for (const { label, re } of DESTRUCTIVE_PATTERNS) if (re.test(statement)) {
78
- found.push({
79
- statement,
80
- kind: label
81
- });
82
- break;
83
- }
84
- return found;
85
- }
86
- /**
87
- * Decide how `db push` should proceed given the plan's destructiveness and
88
- * the invocation context.
89
- *
90
- * - No destructive statements → `apply` (safe to auto-approve).
91
- * - Destructive + `--allow-destructive` → `apply` (operator opted in).
92
- * - Destructive + interactive TTY → `confirm` (prompt before applying).
93
- * - Destructive + non-interactive → `refuse` (never silently drop data in
94
- * CI / scripts / agents).
95
- */
96
- function decidePushSafety(opts) {
97
- if (opts.destructiveCount === 0) return "apply";
98
- if (opts.allowDestructive) return "apply";
99
- return opts.interactive ? "confirm" : "refuse";
100
- }
101
- //#endregion
102
15
  //#region src/schema/generated-column-conflicts.ts
16
+ var import_arg = /* @__PURE__ */ __toESM(require_arg(), 1);
103
17
  /**
104
18
  * Which columns an Atlas plan cannot touch, because a generated column reads
105
19
  * them.
@@ -650,6 +564,15 @@ function tidy(sql) {
650
564
  function acceptsExcludeFlag(domain, args) {
651
565
  return domain === "schema" && args.includes("apply");
652
566
  }
567
+ /**
568
+ * Does this Atlas invocation plan against the scratch database (`--dev-url`)?
569
+ *
570
+ * `schema apply` and `migrate diff` do; `migrate apply`, `hash` and `status`
571
+ * never read it, so `db migrate` has no reason to create one.
572
+ */
573
+ function takesDevUrl(domain, args) {
574
+ return domain === "schema" && args.includes("apply") || domain === "migrate" && args.includes("diff");
575
+ }
653
576
  /** Assemble the full argv, connection flags and all. */
654
577
  function buildAtlasArgs(invocation) {
655
578
  const { domain, args, url, devUrl, excludes = [] } = invocation;
@@ -667,6 +590,52 @@ function buildAtlasArgs(invocation) {
667
590
  if (acceptsExcludeFlag(domain, args)) for (const exclude of excludes) argv.push("--exclude", exclude);
668
591
  return argv;
669
592
  }
593
+ /**
594
+ * `atlas migrate apply`'s own arguments for `rebase db migrate`.
595
+ *
596
+ * `positionals` are what is left of the user's line once every flag the
597
+ * driver knows has been consumed — at most the optional amount Atlas takes. A
598
+ * flag reaching here is a flag Atlas rejects before doing any work.
599
+ */
600
+ function migrateApplyArgs(positionals, baseline) {
601
+ return [
602
+ "apply",
603
+ "--dir",
604
+ "file://drizzle/migrations",
605
+ ...baseline ? ["--baseline", baseline] : [],
606
+ ...positionals.filter((positional) => positional !== "migrate")
607
+ ];
608
+ }
609
+ //#endregion
610
+ //#region src/cli-scratch-database.ts
611
+ var ScratchDatabase = class {
612
+ ops;
613
+ created;
614
+ constructor(ops) {
615
+ this.ops = ops;
616
+ }
617
+ /** Make sure it exists before an Atlas call that plans against it. */
618
+ async ensure(databaseUrl, devDatabaseUrl) {
619
+ if (await this.ops.create(databaseUrl, devDatabaseUrl)) this.created = {
620
+ databaseUrl,
621
+ devDatabaseUrl
622
+ };
623
+ }
624
+ /**
625
+ * Run a command, then drop the scratch database if the command created it.
626
+ * A command that throws skips the drop; one that exits the process never
627
+ * returns here, which is the same thing.
628
+ */
629
+ async around(command) {
630
+ const result = await command();
631
+ const created = this.created;
632
+ if (created) {
633
+ this.created = void 0;
634
+ await this.ops.drop(created.databaseUrl, created.devDatabaseUrl);
635
+ }
636
+ return result;
637
+ }
638
+ };
670
639
  //#endregion
671
640
  //#region src/branch-argv.ts
672
641
  /**
@@ -728,140 +697,6 @@ var TAKES_A_VALUE = /* @__PURE__ */ new Set([
728
697
  "--database-url"
729
698
  ]);
730
699
  //#endregion
731
- //#region src/cli-flags.ts
732
- /**
733
- * The flags each driver command takes, in one place, and the check that
734
- * enforces them.
735
- *
736
- * Every parser in `cli.ts` runs `arg(..., { permissive: true })`, which does not
737
- * mean "be lenient" — it means **an undeclared flag becomes a positional**. So
738
- * `rebase db push --alow-destructive` did not fail: the typo landed in `_`, the
739
- * push ran with the destructive gate still closed, and the developer read the
740
- * refusal as Rebase ignoring the flag they had just typed. `rebase schema
741
- * generate --ouput src/schema.ts` was worse — it wrote the default path and
742
- * said nothing, so the next build compiled a file nobody had regenerated.
743
- *
744
- * The check has to live at the entry point rather than in those parsers, and
745
- * that is not a detail: `db push` and `db generate` re-enter `schemaCommand`
746
- * and `generatePostgresDdlCommand` with the *db* line, so a strict parser
747
- * inside either of them would reject `--allow-destructive` on a line where it
748
- * is correct. One validation, once, against the spec for the command the user
749
- * actually named; the inner parsers keep reading an already-validated line.
750
- *
751
- * Commands with their own argument handling — `db branch` (`unexpectedBranchArgs`),
752
- * `db backup`, `db restore`, `db backups` — are deliberately absent: they own
753
- * their spec, and a second list here would be one more thing to keep in step.
754
- * An absent entry is "not checked here", never "takes nothing".
755
- */
756
- /**
757
- * Flags that reach this process on a line it did not compose.
758
- *
759
- * `rebase db …` hands the driver the user's whole line: the CLI *reads*
760
- * `--database-url` and `--docker` to resolve the database and then relays them
761
- * verbatim, and `--debug` is what `bin/rebase.js` prints after every failure as
762
- * the thing to re-run with. Rejecting any of the three would make the driver
763
- * refuse commands the CLI documents.
764
- */
765
- var RELAYED_FLAGS = {
766
- "--database-url": String,
767
- "--docker": Boolean,
768
- "--debug": Boolean,
769
- "--help": Boolean,
770
- "-h": "--help"
771
- };
772
- /** Keyed by `"<domain> <subcommand>"`, the way the user types it. */
773
- var DRIVER_FLAG_SPECS = {
774
- "db push": {
775
- "--collections": String,
776
- "-c": "--collections",
777
- "--dry-run": Boolean,
778
- "--allow-destructive": Boolean,
779
- "--yes": Boolean,
780
- "-y": "--yes"
781
- },
782
- "db generate": {
783
- "--collections": String,
784
- "-c": "--collections"
785
- },
786
- "db migrate": { "--baseline": String },
787
- "schema generate": {
788
- "--collections": String,
789
- "-c": "--collections",
790
- "--output": String,
791
- "-o": "--output",
792
- "--out": "--output",
793
- "--watch": Boolean,
794
- "-w": "--watch"
795
- },
796
- "schema introspect": {
797
- "--output": String,
798
- "-o": "--output",
799
- "--out": "--output",
800
- "--collections": String,
801
- "-c": "--collections",
802
- "--force": Boolean,
803
- "-f": "--force",
804
- "--schema": String
805
- },
806
- "schema stale": {
807
- "--collections": String,
808
- "-c": "--collections",
809
- "--output": String,
810
- "-o": "--output",
811
- "--out": "--output",
812
- "--fix": Boolean
813
- }
814
- };
815
- /**
816
- * The `--collections` path the user typed, or `null` if they typed none.
817
- *
818
- * Read once, at the entry point, because every command that takes it re-enters
819
- * the generators with the same line and each of them resolved it again — which
820
- * is how "Collections path not found" came to be printed four times before the
821
- * real output, and how a path that does not exist got as far as *writing* an
822
- * empty schema.
823
- *
824
- * Permissive, and it has to be: {@link assertKnownFlags} has already judged the
825
- * line, and `db push` carries flags the `--collections` spec does not name.
826
- */
827
- function collectionsPathIn(args) {
828
- try {
829
- return (0, import_arg.default)({
830
- "--collections": String,
831
- "-c": "--collections"
832
- }, {
833
- argv: args.slice(2),
834
- permissive: true
835
- })["--collections"] ?? null;
836
- } catch {
837
- return null;
838
- }
839
- }
840
- /**
841
- * Reject a flag the named command does not take.
842
- *
843
- * `args` is the driver's whole line — `["db", "push", …]` — so the flags are
844
- * read from `args.slice(2)`, past the two command words.
845
- *
846
- * Throws rather than exiting: `runPluginCommand`'s caller already turns a
847
- * thrown error into one red line and exit 1, and a thrown error is what the
848
- * tests can see.
849
- */
850
- function assertKnownFlags(domain, subcommand, args) {
851
- if (!subcommand) return;
852
- const spec = DRIVER_FLAG_SPECS[`${domain} ${subcommand}`];
853
- if (!spec) return;
854
- try {
855
- (0, import_arg.default)({
856
- ...RELAYED_FLAGS,
857
- ...spec
858
- }, { argv: args.slice(2) });
859
- } catch (err) {
860
- if (err instanceof Error && err.code === "ARG_UNKNOWN_OPTION") throw new Error(`${err.message} — run \`rebase ${domain} --help\` for the options \`${domain} ${subcommand}\` takes.`);
861
- throw err;
862
- }
863
- }
864
- //#endregion
865
700
  //#region src/cli-collections-path.ts
866
701
  /**
867
702
  * `--collections <dir>`, checked once, before anything is written.
@@ -1069,7 +904,7 @@ async function runPluginCommand(args) {
1069
904
  const subcommand = args[1];
1070
905
  assertKnownFlags(domain, subcommand, args);
1071
906
  assertCollectionsPathExists(collectionsPathIn(args));
1072
- if (domain === "db") await dbCommand(subcommand, args);
907
+ if (domain === "db") await scratchDatabase.around(() => dbCommand(subcommand, args));
1073
908
  else if (domain === "schema") await schemaCommand(subcommand, args);
1074
909
  else if (domain === "doctor") await doctorPluginCommand(args);
1075
910
  else {
@@ -1146,6 +981,20 @@ async function stripCarvedOutFromNewestMigration(collectionsPath) {
1146
981
  ], collectionsPath);
1147
982
  return !result.empty;
1148
983
  }
984
+ /**
985
+ * The flags `rebase db` reads for the subcommands it parses itself. Hoisted
986
+ * and named `…FLAGS` so the documentation verifier, which reads specs out of
987
+ * this source, sees them.
988
+ */
989
+ var DB_FLAGS = {
990
+ "--collections": String,
991
+ "--allow-destructive": Boolean,
992
+ "--dry-run": Boolean,
993
+ "--yes": Boolean,
994
+ "--baseline": String,
995
+ "-c": "--collections",
996
+ "-y": "--yes"
997
+ };
1149
998
  async function dbCommand(subcommand, rawArgs) {
1150
999
  const VALID_ACTIONS = [
1151
1000
  "push",
@@ -1172,25 +1021,14 @@ async function dbCommand(subcommand, rawArgs) {
1172
1021
  return;
1173
1022
  }
1174
1023
  if (subcommand === "backup" || subcommand === "restore" || subcommand === "backups") {
1175
- const { backupCommand, restoreCommand, backupsCommand } = await import("./backup-cli-CkjsJEcu.js");
1024
+ const { backupCommand, restoreCommand, backupsCommand } = await import("./backup-cli-24v4OlSp.js");
1176
1025
  if (subcommand === "backup" && backupActionOf(rawArgs) === "list") await backupsCommand(rawArgs);
1177
1026
  else if (subcommand === "backup") await backupCommand(rawArgs);
1178
1027
  else if (subcommand === "restore") await restoreCommand(rawArgs);
1179
1028
  else await backupsCommand(rawArgs);
1180
1029
  return;
1181
1030
  }
1182
- const argsList = (0, import_arg.default)({
1183
- "--collections": String,
1184
- "--allow-destructive": Boolean,
1185
- "--dry-run": Boolean,
1186
- "--yes": Boolean,
1187
- "--baseline": String,
1188
- "-c": "--collections",
1189
- "-y": "--yes"
1190
- }, {
1191
- argv: rawArgs.slice(2),
1192
- permissive: true
1193
- });
1031
+ const argsList = parseDriverLine(DB_FLAGS, rawArgs);
1194
1032
  const collectionsPath = argsList["--collections"] || path.join("..", "config", "collections");
1195
1033
  if (subcommand === "generate") {
1196
1034
  out("");
@@ -1229,7 +1067,7 @@ async function dbCommand(subcommand, rawArgs) {
1229
1067
  }
1230
1068
  const searchContent = readSearchDdl();
1231
1069
  if (searchContent) {
1232
- const { searchColumnDependencies } = await import("./generate-postgres-ddl-logic-CR2xcS7e.js").then((n) => n.o);
1070
+ const { searchColumnDependencies } = await import("./generate-postgres-ddl-logic-Bt2d2mRH.js").then((n) => n.o);
1233
1071
  const migrationConflicts = findGeneratedColumnConflicts(parseColumnMutations(migrationContent), searchColumnDependencies(await loadCollectionsForCli(collectionsPath)));
1234
1072
  const preamble = migrationDropPreamble(migrationConflicts);
1235
1073
  if (preamble) {
@@ -1288,7 +1126,7 @@ async function dbCommand(subcommand, rawArgs) {
1288
1126
  "file://drizzle/schema.sql",
1289
1127
  "--dry-run"
1290
1128
  ], collectionsPath, { captureStdout: true });
1291
- const destructive = detectDestructiveStatements(plan);
1129
+ const destructive = detectDestructiveStatements(plan, databaseUrl ? await queryColumnTypes(databaseUrl) : []);
1292
1130
  const { managed: rebuildable, foreign: unowned } = partitionByOwnership(databaseUrl ? findGeneratedColumnConflicts(parseColumnMutations(plan), await queryGeneratedColumnDependencies(databaseUrl)) : [], collectionsPath ? await getSearchExcludes(collectionsPath) : []);
1293
1131
  if (dryRun) {
1294
1132
  out(chalk.gray(" Step 3/3: --dry-run — nothing was applied."));
@@ -1308,7 +1146,7 @@ async function dbCommand(subcommand, rawArgs) {
1308
1146
  if (unowned.length > 0) outWarn(formatForeignGeneratedColumnBanner(unowned));
1309
1147
  if (destructive.length > 0) {
1310
1148
  outWarn(chalk.yellow(` ⚠️ ${destructive.length} of those DESTROY data:`));
1311
- for (const d of destructive) outWarn(chalk.red(` ${d.kind}: `) + chalk.gray(d.statement.replace(/\s+/g, " ")));
1149
+ for (const d of destructive) outWarn(chalk.red(` ${d.kind}${d.detail ? ` (${d.detail})` : ""}: `) + chalk.gray(d.statement.replace(/\s+/g, " ")));
1312
1150
  outWarn("");
1313
1151
  outWarn(chalk.yellow(" Applying them needs `rebase db push --allow-destructive`. Back up first: rebase db backup"));
1314
1152
  outWarn("");
@@ -1330,7 +1168,7 @@ async function dbCommand(subcommand, rawArgs) {
1330
1168
  if (destructive.length > 0) {
1331
1169
  outWarn(chalk.yellow(` ⚠️ This push includes ${destructive.length} destructive change(s) that will DESTROY data:`));
1332
1170
  outWarn("");
1333
- for (const d of destructive) outWarn(chalk.red(` ${d.kind}: `) + chalk.gray(d.statement.replace(/\s+/g, " ")));
1171
+ for (const d of destructive) outWarn(chalk.red(` ${d.kind}${d.detail ? ` (${d.detail})` : ""}: `) + chalk.gray(d.statement.replace(/\s+/g, " ")));
1334
1172
  outWarn("");
1335
1173
  outWarn(chalk.yellow(" Full planned changes:"));
1336
1174
  outWarn(chalk.gray(plan.trim().split("\n").map((l) => ` ${l}`).join("\n")));
@@ -1375,24 +1213,16 @@ async function dbCommand(subcommand, rawArgs) {
1375
1213
  await reconcilePolicies(databaseUrl, collectionsPath);
1376
1214
  await ensureRlsUserRole(databaseUrl);
1377
1215
  await retireLegacyAuthSchema(databaseUrl);
1378
- if (scratchDatabaseCreatedHere) await dropDevDatabase(databaseUrl, getDevDatabaseUrl(databaseUrl));
1379
1216
  } else outWarn(chalk.yellow(" ⚠️ DATABASE_URL not found in environment, skipping RLS policies application."));
1380
1217
  } else if (subcommand === "migrate") {
1381
1218
  const databaseUrl = process.env.DATABASE_URL;
1382
1219
  if (databaseUrl) await ensureAuthSchemaAndFunctions(databaseUrl);
1383
- const extraArgs = argsList._.filter((arg) => arg !== "migrate");
1384
1220
  const baseline = argsList["--baseline"];
1385
1221
  if (baseline) {
1386
1222
  out(chalk.gray(` Recording ${baseline} as already applied, then migrating from the next one.`));
1387
1223
  out("");
1388
1224
  }
1389
- await runAtlas("migrate", [
1390
- "apply",
1391
- "--dir",
1392
- "file://drizzle/migrations",
1393
- ...baseline ? ["--baseline", baseline] : [],
1394
- ...extraArgs
1395
- ], collectionsPath);
1225
+ await runAtlas("migrate", migrateApplyArgs(argsList._, baseline), collectionsPath);
1396
1226
  if (databaseUrl) {
1397
1227
  await ensureRlsUserRole(databaseUrl);
1398
1228
  await retireLegacyAuthSchema(databaseUrl);
@@ -1431,8 +1261,8 @@ async function ensureAuthSchemaAndFunctions(databaseUrl) {
1431
1261
  async function ensureAuthTables(databaseUrl, collectionsPath) {
1432
1262
  try {
1433
1263
  const { drizzle } = await import("drizzle-orm/node-postgres");
1434
- const { ensureAuthTablesExist } = await import("./ensure-tables-BnEvEJPr.js").then((n) => n.n);
1435
- const { loadCollections } = await import("./doctor-Gzzd8wTq.js").then((n) => n.t);
1264
+ const { ensureAuthTablesExist } = await import("./ensure-tables-Dhn9KM3B.js").then((n) => n.n);
1265
+ const { loadCollections } = await import("./doctor-Cb2thZ8s.js").then((n) => n.t);
1436
1266
  const { Client } = await import("pg");
1437
1267
  const authCollection = (await loadCollections(path.resolve(process.cwd(), collectionsPath))).find((c) => {
1438
1268
  const a = c.auth;
@@ -1544,8 +1374,8 @@ async function applyPolicies(databaseUrl) {
1544
1374
  */
1545
1375
  async function reconcilePolicies(databaseUrl, collectionsPath) {
1546
1376
  try {
1547
- const { checkPolicyDrift, dropOrphanedPolicies, formatPolicyDrift, hasDrift } = await import("./policy-drift-B0GDRh7_.js").then((n) => n.a);
1548
- const { loadCollections } = await import("./doctor-Gzzd8wTq.js").then((n) => n.t);
1377
+ const { checkPolicyDrift, dropOrphanedPolicies, formatPolicyDrift, hasDrift } = await import("./policy-drift-DljYdrpW.js").then((n) => n.a);
1378
+ const { loadCollections } = await import("./doctor-Cb2thZ8s.js").then((n) => n.t);
1549
1379
  const collections = await loadCollections(path.resolve(process.cwd(), collectionsPath));
1550
1380
  const { Client } = await import("pg");
1551
1381
  const client = new Client({ connectionString: databaseUrl });
@@ -1627,7 +1457,7 @@ async function branchCommand(rawArgs) {
1627
1457
  process.exit(1);
1628
1458
  }
1629
1459
  const { DatabasePoolManager } = await import("./databasePoolManager-Bj5FbeAs.js").then((n) => n.n);
1630
- const { BranchService } = await import("./BranchService-CucnFcSE.js").then((n) => n.n);
1460
+ const { BranchService } = await import("./BranchService-DGPL6G_C.js").then((n) => n.n);
1631
1461
  const { drizzle } = await import("drizzle-orm/node-postgres");
1632
1462
  const { Pool } = await import("pg");
1633
1463
  const pool = new Pool({
@@ -1857,8 +1687,14 @@ function formatBytes(bytes) {
1857
1687
  function timeAgo(date) {
1858
1688
  return formatRelativeTime(date, { maxMs: Number.POSITIVE_INFINITY }) ?? "unknown";
1859
1689
  }
1860
- /** Set when this process created the Atlas scratch database; see `ensureDevDatabaseExists`. */
1861
- var scratchDatabaseCreatedHere = false;
1690
+ /**
1691
+ * The Atlas scratch database this process created, dropped once the db
1692
+ * command that needed it succeeds — see `ScratchDatabase`.
1693
+ */
1694
+ var scratchDatabase = new ScratchDatabase({
1695
+ create: ensureDevDatabaseExists,
1696
+ drop: dropDevDatabase
1697
+ });
1862
1698
  async function runAtlas(domain, args, collectionsPath, opts = {}) {
1863
1699
  const atlasBin = resolveLocalBin("atlas");
1864
1700
  if (!atlasBin) {
@@ -1911,7 +1747,7 @@ async function runAtlas(domain, args, collectionsPath, opts = {}) {
1911
1747
  }
1912
1748
  await checkDatabaseConnectivity(databaseUrl);
1913
1749
  const devDatabaseUrl = getDevDatabaseUrl(databaseUrl);
1914
- if (await ensureDevDatabaseExists(databaseUrl, devDatabaseUrl)) scratchDatabaseCreatedHere = true;
1750
+ if (takesDevUrl(domain, args)) await scratchDatabase.ensure(databaseUrl, devDatabaseUrl);
1915
1751
  const atlasUrl = forLibpq(databaseUrl);
1916
1752
  const atlasDevUrl = forLibpq(devDatabaseUrl);
1917
1753
  const excludes = [];
@@ -2053,9 +1889,9 @@ async function schemaStaleCommand(rawArgs) {
2053
1889
  const schemaFile = path.resolve(process.cwd(), outputPath);
2054
1890
  const fix = Boolean(argsList["--fix"]);
2055
1891
  const generatedExists = fs.existsSync(schemaFile);
2056
- const { loadCollections } = await import("./doctor-Gzzd8wTq.js").then((n) => n.t);
1892
+ const { loadCollections } = await import("./doctor-Cb2thZ8s.js").then((n) => n.t);
2057
1893
  const { findLegacyForeignKeyNames, compareGeneratedDeclarations, staleVerdict } = await import("./generated-schema-staleness-Di9c4ZxN.js").then((n) => n.r);
2058
- const { generateSchema } = await import("./generate-drizzle-schema-logic-DFa9qy9u.js").then((n) => n.n);
1894
+ const { generateSchema } = await import("./generate-drizzle-schema-logic-BfzK7UQd.js").then((n) => n.n);
2059
1895
  const { relationalCollections } = await import("@rebasepro/common");
2060
1896
  let stale = [];
2061
1897
  let differences = [];