@rebasepro/server-postgres 0.17.3-canary.gdd23447 → 0.18.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 (149) hide show
  1. package/LICENSE +0 -1
  2. package/README.md +31 -0
  3. package/dist/backup-argv.d.ts +19 -0
  4. package/dist/branch-argv.d.ts +28 -0
  5. package/dist/branch-prune.d.ts +77 -0
  6. package/dist/cli-collections-path.d.ts +31 -0
  7. package/dist/cli-errors.d.ts +123 -0
  8. package/dist/cli-flags.d.ts +101 -0
  9. package/dist/cli-helpers.d.ts +94 -10
  10. package/dist/collection-index-C7LopMyX.js +247 -0
  11. package/dist/collection-index-C7LopMyX.js.map +1 -0
  12. package/dist/databasePoolManager.d.ts +27 -0
  13. package/dist/{ensure-collection-policies-UCqgv_8c.js → ensure-collection-policies-CagOzEPS.js} +6 -6
  14. package/dist/{ensure-collection-policies-UCqgv_8c.js.map → ensure-collection-policies-CagOzEPS.js.map} +1 -1
  15. package/dist/{ensure-collection-tables-DgVixhX3.js → ensure-collection-tables-DbAhrjnG.js} +614 -52
  16. package/dist/ensure-collection-tables-DbAhrjnG.js.map +1 -0
  17. package/dist/index.d.ts +12 -0
  18. package/dist/index.es.js +19307 -3059
  19. package/dist/index.es.js.map +1 -1
  20. package/dist/{rls-enforcement-DvAbL9YJ.js → rls-enforcement-ClJoTboT.js} +53 -189
  21. package/dist/rls-enforcement-ClJoTboT.js.map +1 -0
  22. package/dist/schema/column-type-drift.d.ts +79 -0
  23. package/dist/schema/doctor.d.ts +42 -0
  24. package/dist/schema/ensure-collection-tables.d.ts +90 -0
  25. package/dist/schema/generate-next-step.d.ts +30 -0
  26. package/dist/schema/generate-postgres-ddl-logic.d.ts +28 -0
  27. package/dist/schema/generated-schema-staleness.d.ts +37 -0
  28. package/dist/schema/introspect-db-queries.d.ts +34 -0
  29. package/dist/security/rls-enforcement.d.ts +63 -2
  30. package/dist/services/BranchService.d.ts +86 -1
  31. package/dist/services/collection-helpers.d.ts +1 -1
  32. package/dist/services/realtimeService.d.ts +30 -2
  33. package/dist/services/row-pipeline.d.ts +12 -1
  34. package/dist/utils/pg-error-utils.d.ts +3 -1
  35. package/dist/utils/pg-numeric-number-patch.d.ts +31 -0
  36. package/package.json +32 -27
  37. package/dist/backup-service-DCk7KhhL.js +0 -9044
  38. package/dist/backup-service-DCk7KhhL.js.map +0 -1
  39. package/dist/collection-index-BRUg10H5.js +0 -5690
  40. package/dist/collection-index-BRUg10H5.js.map +0 -1
  41. package/dist/connection-GOKU3Hu5.js +0 -277
  42. package/dist/connection-GOKU3Hu5.js.map +0 -1
  43. package/dist/ensure-collection-tables-DgVixhX3.js.map +0 -1
  44. package/dist/rls-bootstrap-sql-B5C9LoJ6.js +0 -244
  45. package/dist/rls-bootstrap-sql-B5C9LoJ6.js.map +0 -1
  46. package/dist/rls-enforcement-DvAbL9YJ.js.map +0 -1
  47. package/dist/src-DiB5RP2Z.js +0 -541
  48. package/dist/src-DiB5RP2Z.js.map +0 -1
  49. package/dist/websocket-BZ4H5wUz.js +0 -8198
  50. package/dist/websocket-BZ4H5wUz.js.map +0 -1
  51. package/src/PostgresAdapter.ts +0 -91
  52. package/src/PostgresBackendDriver.ts +0 -1857
  53. package/src/PostgresBootstrapper.ts +0 -1157
  54. package/src/auth/ensure-tables.ts +0 -896
  55. package/src/auth/schema-version.ts +0 -260
  56. package/src/auth/services.ts +0 -1610
  57. package/src/backup/backup-cli.ts +0 -520
  58. package/src/backup/backup-cron.ts +0 -212
  59. package/src/backup/backup-logic.ts +0 -93
  60. package/src/backup/backup-service.ts +0 -485
  61. package/src/backup/index.ts +0 -12
  62. package/src/backup/pg-tools.ts +0 -403
  63. package/src/backup/retention.ts +0 -86
  64. package/src/cli-errors.ts +0 -265
  65. package/src/cli-helpers.ts +0 -569
  66. package/src/cli-output.ts +0 -43
  67. package/src/cli.ts +0 -1339
  68. package/src/collections/PostgresCollectionRegistry.ts +0 -108
  69. package/src/collections/buildRegistry.ts +0 -68
  70. package/src/collections/validate-relations.ts +0 -402
  71. package/src/connection.ts +0 -333
  72. package/src/data-transformer.ts +0 -845
  73. package/src/databasePoolManager.ts +0 -95
  74. package/src/history/HistoryService.ts +0 -254
  75. package/src/history/ensure-history-table.ts +0 -53
  76. package/src/index.ts +0 -19
  77. package/src/interfaces.ts +0 -60
  78. package/src/module-dir.ts +0 -7
  79. package/src/schema/atlas-argv.ts +0 -94
  80. package/src/schema/auth-schema.ts +0 -298
  81. package/src/schema/auth-users-columns.ts +0 -131
  82. package/src/schema/carved-out-migration.ts +0 -404
  83. package/src/schema/classify-change.ts +0 -436
  84. package/src/schema/collection-index.ts +0 -427
  85. package/src/schema/collections-schema-version.ts +0 -103
  86. package/src/schema/destructive-sql.ts +0 -94
  87. package/src/schema/doctor-cli.ts +0 -66
  88. package/src/schema/doctor-policy-checks.ts +0 -105
  89. package/src/schema/doctor.ts +0 -837
  90. package/src/schema/drizzle-ddl.ts +0 -33
  91. package/src/schema/dynamic-tables.test.ts +0 -302
  92. package/src/schema/dynamic-tables.ts +0 -293
  93. package/src/schema/ensure-collection-policies.ts +0 -198
  94. package/src/schema/ensure-collection-tables.test.ts +0 -518
  95. package/src/schema/ensure-collection-tables.ts +0 -1267
  96. package/src/schema/generate-drizzle-schema-logic.ts +0 -957
  97. package/src/schema/generate-drizzle-schema.ts +0 -143
  98. package/src/schema/generate-postgres-ddl-logic.ts +0 -1339
  99. package/src/schema/generate-postgres-ddl.ts +0 -180
  100. package/src/schema/generate-schema-commit.ts +0 -267
  101. package/src/schema/generated-schema-staleness.ts +0 -284
  102. package/src/schema/introspect-db-constraints.ts +0 -385
  103. package/src/schema/introspect-db-inference.ts +0 -255
  104. package/src/schema/introspect-db-logic.ts +0 -1387
  105. package/src/schema/introspect-db-naming.ts +0 -15
  106. package/src/schema/introspect-db-project.ts +0 -78
  107. package/src/schema/introspect-db-queries.ts +0 -326
  108. package/src/schema/introspect-db-structure.ts +0 -670
  109. package/src/schema/introspect-db-types.ts +0 -56
  110. package/src/schema/introspect-db.ts +0 -257
  111. package/src/schema/introspect-runtime.test.ts +0 -260
  112. package/src/schema/introspect-runtime.ts +0 -327
  113. package/src/schema/non-sql-collections.test.ts +0 -131
  114. package/src/schema/rls-bootstrap-sql.ts +0 -288
  115. package/src/schema/search-column.ts +0 -643
  116. package/src/schema/test-schema.ts +0 -11
  117. package/src/schema/vector-index.ts +0 -491
  118. package/src/security/anonymous-grants.test.ts +0 -73
  119. package/src/security/policy-drift.test.ts +0 -435
  120. package/src/security/policy-drift.ts +0 -473
  121. package/src/security/rls-enforcement.ts +0 -505
  122. package/src/services/BranchService.ts +0 -326
  123. package/src/services/FetchService.ts +0 -2115
  124. package/src/services/PersistService.ts +0 -557
  125. package/src/services/RelationService.ts +0 -978
  126. package/src/services/RelationWriteService.ts +0 -653
  127. package/src/services/cdc/CdcListener.ts +0 -103
  128. package/src/services/cdc/junction-tables.ts +0 -91
  129. package/src/services/cdc/trigger-cdc.ts +0 -173
  130. package/src/services/channel-bus/ChannelBus.ts +0 -44
  131. package/src/services/channel-bus/PostgresChannelBus.ts +0 -299
  132. package/src/services/channel-bus/index.ts +0 -114
  133. package/src/services/channel-history.ts +0 -424
  134. package/src/services/channel-presence.ts +0 -185
  135. package/src/services/collection-helpers.ts +0 -420
  136. package/src/services/dataService.ts +0 -256
  137. package/src/services/index.ts +0 -14
  138. package/src/services/junction-writes.ts +0 -295
  139. package/src/services/nested-path.ts +0 -145
  140. package/src/services/pg-notify-listener.ts +0 -151
  141. package/src/services/realtimeService.ts +0 -2545
  142. package/src/services/row-pipeline.ts +0 -240
  143. package/src/services/write-denial.ts +0 -55
  144. package/src/types.ts +0 -4
  145. package/src/utils/connection-string.ts +0 -58
  146. package/src/utils/drizzle-conditions.ts +0 -2743
  147. package/src/utils/pg-array-null-patch.ts +0 -42
  148. package/src/utils/pg-error-utils.ts +0 -391
  149. package/src/websocket.ts +0 -855
@@ -1,404 +0,0 @@
1
- /**
2
- * Keep the objects Rebase carved out of Atlas's view out of the migrations
3
- * Atlas writes.
4
- *
5
- * `db push` protects them with `--exclude`. `atlas migrate diff` has no such
6
- * flag — measured on the pinned 1.2.3, `--exclude` is rejected outright with
7
- * `unknown flag`, and the `exclude` attribute of an `atlas.hcl` `env` block is
8
- * accepted and then silently ignored on that path. So the diff sees the search
9
- * column that the migration directory's own appended DDL created when it
10
- * replayed, does not see it in `schema.sql`, and plans a drop:
11
- *
12
- * ALTER TABLE "public"."talents" DROP COLUMN "search_vector";
13
- *
14
- * That statement is removed here, after Atlas has written the file and before
15
- * anything is appended to it — so the input is always Atlas's own plain output,
16
- * never the dollar-quoted helper bodies that get appended afterwards.
17
- *
18
- * The drop does not arrive alone. Atlas folds every change to one table into a
19
- * single statement, so a real edit in the same run produces
20
- *
21
- * ALTER TABLE "public"."talents"
22
- * DROP COLUMN "search_vector", DROP COLUMN "interests", ADD COLUMN "headline" text NULL;
23
- *
24
- * and dropping the whole statement would silently throw away a migration the
25
- * project asked for. The removal is therefore per *clause*.
26
- *
27
- * Fail-safe throughout: a statement that mentions a carved-out object but does
28
- * not match a shape this understands is left exactly as it is and reported as
29
- * `unhandled`, so the caller can say so out loud. A spurious drop the developer
30
- * can see beats a migration quietly rewritten wrong.
31
- */
32
-
33
- /** Anything Postgres accepts in an unquoted identifier past ASCII. */
34
- const IDENT = "(?:\"(?:[^\"]|\"\")*\"|[A-Za-z_\\u0080-\\uffff][\\w$\\u0080-\\uffff]*)";
35
- const QUALIFIED = `${IDENT}(?:\\s*\\.\\s*${IDENT})*`;
36
-
37
- const ALTER_TABLE_RE = new RegExp(`^ALTER\\s+TABLE\\s+(?:ONLY\\s+)?(${QUALIFIED})\\s+([\\s\\S]+)$`, "i");
38
- const DROP_COLUMN_RE = new RegExp(`^DROP\\s+(?:COLUMN\\s+)?(?:IF\\s+EXISTS\\s+)?(${IDENT})$`, "i");
39
- const DROP_INDEX_RE = new RegExp(`^DROP\\s+INDEX\\s+(?:CONCURRENTLY\\s+)?(?:IF\\s+EXISTS\\s+)?(${QUALIFIED})$`, "i");
40
-
41
- /**
42
- * Does this statement destroy anything at all?
43
- *
44
- * The gate on reporting. A statement that merely *names* a carved-out object —
45
- * `COMMENT ON COLUMN … "search_vector"` — is not a problem and must not be
46
- * reported as one, because an unrewritable drop stops the caller outright.
47
- */
48
- const MENTIONS_DROP = /\bDROP\b/i;
49
-
50
- /** One `schema.table.object` exclude pattern, taken apart. */
51
- export interface CarvedOutObject {
52
- schema: string;
53
- table: string;
54
- object: string;
55
- }
56
-
57
- export interface StripResult {
58
- /** The migration text with the carved-out clauses gone. */
59
- sql: string;
60
- /** What was removed, as SQL fragments, for reporting. */
61
- removed: string[];
62
- /**
63
- * Statements naming a carved-out object that this could not rewrite with
64
- * confidence. Left untouched in `sql`.
65
- */
66
- unhandled: string[];
67
- /** True when nothing but comments and whitespace survives. */
68
- empty: boolean;
69
- }
70
-
71
- /**
72
- * Split `schema.table.object` patterns. Anything not in three parts is
73
- * ignored rather than guessed at — the two-part form is the one Atlas itself
74
- * silently mis-reads, and repeating that mistake here would be worse than
75
- * doing nothing.
76
- */
77
- export function parseExcludePatterns(patterns: string[]): CarvedOutObject[] {
78
- const parsed: CarvedOutObject[] = [];
79
- for (const pattern of patterns) {
80
- const parts = pattern.split(".");
81
- if (parts.length !== 3) continue;
82
- if (parts.some((p) => p.length === 0)) continue;
83
- parsed.push({ schema: parts[0], table: parts[1], object: parts[2] });
84
- }
85
- return parsed;
86
- }
87
-
88
- /** `"public"` and `public` are the same name; `"Public"` is not `public`. */
89
- function unquoteIdentifier(raw: string): string {
90
- const trimmed = raw.trim();
91
- if (trimmed.startsWith("\"") && trimmed.endsWith("\"") && trimmed.length >= 2) {
92
- return trimmed.slice(1, -1).replace(/""/g, "\"");
93
- }
94
- // An unquoted identifier is folded to lower case by Postgres.
95
- return trimmed.toLowerCase();
96
- }
97
-
98
- /** Split a dotted identifier at its top-level dots, respecting quoting. */
99
- function splitQualifiedName(raw: string): string[] | null {
100
- const parts: string[] = [];
101
- let current = "";
102
- let inQuote = false;
103
- for (let i = 0; i < raw.length; i++) {
104
- const ch = raw[i];
105
- if (inQuote) {
106
- if (ch === "\"") {
107
- if (raw[i + 1] === "\"") { current += "\"\""; i++; continue; }
108
- inQuote = false;
109
- }
110
- current += ch;
111
- continue;
112
- }
113
- if (ch === "\"") { inQuote = true; current += ch; continue; }
114
- if (ch === ".") { parts.push(current); current = ""; continue; }
115
- current += ch;
116
- }
117
- if (inQuote) return null;
118
- parts.push(current);
119
- return parts.map(unquoteIdentifier);
120
- }
121
-
122
- /**
123
- * One statement located in the source text, with the comment lines that
124
- * introduce it. Atlas writes `-- Modify "talents" table` above each statement;
125
- * removing the statement without its comment leaves a caption over nothing.
126
- */
127
- interface LocatedStatement {
128
- /** Offset of the first character of the leading comment block. */
129
- start: number;
130
- /** Offset just past the terminating semicolon. */
131
- end: number;
132
- /** Offset of the first character of the statement itself. */
133
- bodyStart: number;
134
- /** The statement without its leading comments or trailing semicolon. */
135
- body: string;
136
- }
137
-
138
- /**
139
- * Walk the script and locate every statement. Quote-, comment- and
140
- * dollar-aware: the appended halves of a migration are not passed here, but a
141
- * splitter that shreds a `DO $tag$ ... $tag$` block is a trap worth not
142
- * leaving behind — it is how the derived-names renderer broke.
143
- *
144
- * Returns null if the text ends inside a quote or a dollar-quoted body, or
145
- * trails off in a statement with no semicolon. Either means this cannot be
146
- * reasoned about safely.
147
- */
148
- function locateStatements(sql: string): LocatedStatement[] | null {
149
- const statements: LocatedStatement[] = [];
150
- let i = 0;
151
- let statementStart = 0;
152
- let sawCode = false;
153
-
154
- const pushStatement = (endExclusive: number) => {
155
- const leading = sql.slice(statementStart, endExclusive);
156
- // The leading run of comment/blank lines belongs to this statement.
157
- let consumed = 0;
158
- for (const line of leading.split("\n")) {
159
- const trimmed = line.trim();
160
- if (trimmed === "" || trimmed.startsWith("--")) {
161
- consumed += line.length + 1;
162
- continue;
163
- }
164
- break;
165
- }
166
- const bodyStart = statementStart + consumed;
167
- statements.push({
168
- start: statementStart,
169
- end: endExclusive,
170
- bodyStart,
171
- body: sql.slice(bodyStart, endExclusive).replace(/;\s*$/, "").trim()
172
- });
173
- };
174
-
175
- while (i < sql.length) {
176
- const ch = sql[i];
177
-
178
- if (ch === "-" && sql[i + 1] === "-") {
179
- const nl = sql.indexOf("\n", i);
180
- i = nl === -1 ? sql.length : nl + 1;
181
- continue;
182
- }
183
- if (ch === "/" && sql[i + 1] === "*") {
184
- const close = sql.indexOf("*/", i + 2);
185
- if (close === -1) return null;
186
- i = close + 2;
187
- sawCode = true;
188
- continue;
189
- }
190
- if (ch === "'" || ch === "\"") {
191
- const quote = ch;
192
- i++;
193
- let closed = false;
194
- while (i < sql.length) {
195
- if (sql[i] === quote) {
196
- if (sql[i + 1] === quote) { i += 2; continue; }
197
- i++;
198
- closed = true;
199
- break;
200
- }
201
- i++;
202
- }
203
- if (!closed) return null;
204
- sawCode = true;
205
- continue;
206
- }
207
- if (ch === "$") {
208
- const tag = /^\$(?:[A-Za-z_\u0080-\uffff][\w\u0080-\uffff]*)?\$/.exec(sql.slice(i));
209
- if (tag) {
210
- const close = sql.indexOf(tag[0], i + tag[0].length);
211
- if (close === -1) return null;
212
- i = close + tag[0].length;
213
- sawCode = true;
214
- continue;
215
- }
216
- }
217
- if (ch === ";") {
218
- pushStatement(i + 1);
219
- i++;
220
- statementStart = i;
221
- sawCode = false;
222
- continue;
223
- }
224
- if (!/\s/.test(ch)) sawCode = true;
225
- i++;
226
- }
227
-
228
- // Trailing text with no semicolon: only comments and whitespace is fine.
229
- if (sawCode) return null;
230
- return statements;
231
- }
232
-
233
- /** Split an ALTER TABLE action list at its top-level commas. */
234
- function splitClauses(actions: string): string[] | null {
235
- const clauses: string[] = [];
236
- let depth = 0;
237
- let current = "";
238
- let i = 0;
239
- while (i < actions.length) {
240
- const ch = actions[i];
241
- if (ch === "'" || ch === "\"") {
242
- const quote = ch;
243
- current += ch;
244
- i++;
245
- let closed = false;
246
- while (i < actions.length) {
247
- current += actions[i];
248
- if (actions[i] === quote) {
249
- if (actions[i + 1] === quote) { current += actions[i + 1]; i += 2; continue; }
250
- i++;
251
- closed = true;
252
- break;
253
- }
254
- i++;
255
- }
256
- if (!closed) return null;
257
- continue;
258
- }
259
- if (ch === "(") { depth++; current += ch; i++; continue; }
260
- if (ch === ")") { depth--; current += ch; i++; continue; }
261
- if (ch === "," && depth === 0) { clauses.push(current); current = ""; i++; continue; }
262
- current += ch;
263
- i++;
264
- }
265
- if (depth !== 0) return null;
266
- clauses.push(current);
267
- return clauses.map((c) => c.trim()).filter((c) => c.length > 0);
268
- }
269
-
270
- /**
271
- * Remove every clause and statement that would drop one of `patterns`.
272
- *
273
- * `patterns` are the `schema.table.object` strings the CLI already builds for
274
- * Atlas's `--exclude`, so the diff path and the apply path protect exactly the
275
- * same set by construction.
276
- */
277
- export function stripCarvedOutStatements(migrationSql: string, patterns: string[]): StripResult {
278
- const carved = parseExcludePatterns(patterns);
279
- if (carved.length === 0) {
280
- return { sql: migrationSql, removed: [], unhandled: [], empty: isBlank(migrationSql) };
281
- }
282
-
283
- const located = locateStatements(migrationSql);
284
- if (located === null) {
285
- // Nothing can be trusted about the offsets, so nothing moves. Worth
286
- // stopping the caller over only if one of ours is being dropped in
287
- // there; otherwise this file has nothing to do with us.
288
- const atStake = MENTIONS_DROP.test(migrationSql)
289
- && carved.some((c) => migrationSql.includes(c.object));
290
- return {
291
- sql: migrationSql,
292
- removed: [],
293
- unhandled: atStake ? [migrationSql.trim()] : [],
294
- empty: isBlank(migrationSql)
295
- };
296
- }
297
-
298
- const removed: string[] = [];
299
- const unhandled: string[] = [];
300
- // Edits as [start, end) → replacement, applied back-to-front so the
301
- // offsets of the ones still pending stay valid.
302
- const edits: { start: number; end: number; replacement: string }[] = [];
303
-
304
- for (const statement of located) {
305
- if (!carved.some((c) => statement.body.includes(c.object))) continue;
306
-
307
- const dropIndex = DROP_INDEX_RE.exec(statement.body);
308
- if (dropIndex) {
309
- const parts = splitQualifiedName(dropIndex[1]);
310
- const name = parts?.[parts.length - 1];
311
- const schema = parts && parts.length > 1 ? parts[parts.length - 2] : undefined;
312
- if (name !== undefined && carved.some((c) =>
313
- c.object === name && (schema === undefined || c.schema === schema))) {
314
- removed.push(statement.body);
315
- edits.push({ start: statement.start, end: statement.end, replacement: "" });
316
- } else {
317
- // A near-miss on the name — some other index whose name
318
- // contains ours. Left alone, and still a drop, so still
319
- // reported.
320
- unhandled.push(statement.body);
321
- }
322
- continue;
323
- }
324
-
325
- // Anything else naming a carved-out object is only our business if it
326
- // destroys something. `COMMENT ON COLUMN … "search_vector"` does not.
327
- const alter = ALTER_TABLE_RE.exec(statement.body);
328
- if (!alter) {
329
- if (MENTIONS_DROP.test(statement.body)) unhandled.push(statement.body);
330
- continue;
331
- }
332
-
333
- const target = splitQualifiedName(alter[1]);
334
- if (!target) {
335
- if (MENTIONS_DROP.test(statement.body)) unhandled.push(statement.body);
336
- continue;
337
- }
338
- const table = target[target.length - 1];
339
- const schema = target.length > 1 ? target[target.length - 2] : undefined;
340
-
341
- const clauses = splitClauses(alter[2]);
342
- if (!clauses) {
343
- if (MENTIONS_DROP.test(statement.body)) unhandled.push(statement.body);
344
- continue;
345
- }
346
-
347
- const survivors: string[] = [];
348
- let removedHere = 0;
349
- let unhandledHere = false;
350
- for (const clause of clauses) {
351
- const drop = DROP_COLUMN_RE.exec(clause);
352
- const column = drop ? unquoteIdentifier(drop[1]) : undefined;
353
- const isCarved = column !== undefined && carved.some((c) =>
354
- c.object === column &&
355
- c.table === table &&
356
- (schema === undefined || c.schema === schema));
357
- if (isCarved) {
358
- removed.push(`ALTER TABLE ${alter[1]}: ${clause}`);
359
- removedHere++;
360
- continue;
361
- }
362
- // A clause naming a carved-out object in some other shape is not
363
- // something to guess at — if it destroys it.
364
- if (MENTIONS_DROP.test(clause) && carved.some((c) => clause.includes(c.object))) {
365
- unhandledHere = true;
366
- }
367
- survivors.push(clause);
368
- }
369
-
370
- if (unhandledHere) unhandled.push(statement.body);
371
- if (removedHere === 0) continue;
372
-
373
- if (survivors.length === 0) {
374
- edits.push({ start: statement.start, end: statement.end, replacement: "" });
375
- } else {
376
- edits.push({
377
- start: statement.bodyStart,
378
- end: statement.end,
379
- replacement: `ALTER TABLE ${alter[1]} ${survivors.join(", ")};`
380
- });
381
- }
382
- }
383
-
384
- let sql = migrationSql;
385
- for (const edit of edits.sort((a, b) => b.start - a.start)) {
386
- sql = sql.slice(0, edit.start) + edit.replacement + sql.slice(edit.end);
387
- }
388
- if (edits.length > 0) sql = tidy(sql);
389
-
390
- return { sql, removed, unhandled, empty: isBlank(sql) };
391
- }
392
-
393
- /** Only comments and whitespace left — nothing a migration would do. */
394
- function isBlank(sql: string): boolean {
395
- return sql
396
- .split("\n")
397
- .every((line) => line.trim() === "" || line.trim().startsWith("--"));
398
- }
399
-
400
- /** Collapse the blank runs an excision leaves behind. */
401
- function tidy(sql: string): string {
402
- const trimmed = sql.replace(/\n{3,}/g, "\n\n").replace(/^\s*\n/, "").trimEnd();
403
- return trimmed.length === 0 ? "" : `${trimmed}\n`;
404
- }