@everystack/cli 0.3.19 → 0.3.22

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.
@@ -65,6 +65,12 @@ export type SchemaChange =
65
65
  | { kind: 'renameSourceMissing'; table: string; column: string; from: string }
66
66
  /** An unmarked drop+add that *might* be a rename — a hint comment, the SQL still drops+adds. */
67
67
  | { kind: 'renameHint'; table: string; from: string; to: string }
68
+ /** A table-level `renamedFrom` satisfied: old table present, new absent → one RENAME, data preserved. */
69
+ | { kind: 'renameTable'; from: string; to: string }
70
+ /** A table marker whose rename is already applied (new name present) — inert, a notice. */
71
+ | { kind: 'renameTableSatisfied'; table: string; from: string }
72
+ /** A table marker pointing at a table the database doesn't have — fell back to CREATE, a notice. */
73
+ | { kind: 'renameTableSourceMissing'; table: string; from: string }
68
74
  /** A new enum type → `CREATE TYPE … AS ENUM (…)`, emitted before the tables that use it. */
69
75
  | { kind: 'createType'; name: string; values: string[] }
70
76
  /** A value appended to an existing enum → `ALTER TYPE … ADD VALUE` (carries a -- WARNING). */
@@ -86,6 +92,14 @@ export type SchemaChange =
86
92
  */
87
93
  export type RenameMap = Record<string, Record<string, string>>;
88
94
 
95
+ /**
96
+ * Table-level rename intent: qualified new name → qualified old name. Produced
97
+ * by `compileTableRenames` off the Models' `renamedFrom` option; consumed by
98
+ * `diffSchema`'s pre-pass, which rewrites the current snapshot (old table seen
99
+ * as the new name) so every downstream diff lands on the renamed table.
100
+ */
101
+ export type TableRenameMap = Record<string, string>;
102
+
89
103
  // ---------------------------------------------------------------------------
90
104
  // Normalization — so semantically-equal expressions don't read as drift.
91
105
  // ---------------------------------------------------------------------------
@@ -232,8 +246,9 @@ export function normalizeCheck(expr: string): string {
232
246
  * replaced constraint never clashes with its old self and a column is never dropped out
233
247
  * from under a constraint that still references it.
234
248
  */
235
- export function diffSchema(desired: SchemaSnapshot, current: SchemaSnapshot, renames: RenameMap = {}): SchemaChange[] {
249
+ export function diffSchema(desired: SchemaSnapshot, current: SchemaSnapshot, renames: RenameMap = {}, tableRenames: TableRenameMap = {}): SchemaChange[] {
236
250
  const creates: SchemaChange[] = [];
251
+ const renameTables: SchemaChange[] = [];
237
252
  const renameColumns: SchemaChange[] = [];
238
253
  const addColumns: SchemaChange[] = [];
239
254
  const alters: SchemaChange[] = [];
@@ -248,6 +263,28 @@ export function diffSchema(desired: SchemaSnapshot, current: SchemaSnapshot, ren
248
263
  const currentByName = new Map(current.tables.map((t) => [t.table, t]));
249
264
  const desiredNames = new Set(desired.tables.map((t) => t.table));
250
265
 
266
+ // Table-rename pre-pass: honor the Models' table-level `renamedFrom` before
267
+ // any matching. A satisfied rename rewrites the current snapshot — the old
268
+ // table is seen under its new name — so every downstream diff (columns,
269
+ // constraints, indexes, drops) lands on the renamed table, and the old name
270
+ // never reaches the dropTables sweep. Mirrors the column-rename semantics:
271
+ // new-name-present → inert notice; source missing → CREATE path + notice.
272
+ for (const [to, from] of Object.entries(tableRenames)) {
273
+ if (!desiredNames.has(to)) continue; // the marker's model isn't in this diff
274
+ if (currentByName.has(to)) {
275
+ notices.push({ kind: 'renameTableSatisfied', table: to, from });
276
+ continue;
277
+ }
278
+ const source = currentByName.get(from);
279
+ if (!source) {
280
+ notices.push({ kind: 'renameTableSourceMissing', table: to, from });
281
+ continue;
282
+ }
283
+ renameTables.push({ kind: 'renameTable', from, to });
284
+ currentByName.delete(from);
285
+ currentByName.set(to, { ...source, table: to });
286
+ }
287
+
251
288
  for (const d of desired.tables) {
252
289
  const c = currentByName.get(d.table);
253
290
  if (!c) {
@@ -274,18 +311,21 @@ export function diffSchema(desired: SchemaSnapshot, current: SchemaSnapshot, ren
274
311
  diffIndexes(d, c, createIndexes, dropIndexes);
275
312
  }
276
313
 
277
- for (const c of current.tables) {
314
+ // Sweep the POST-RENAME view: a renamed-away old name is not a drop.
315
+ for (const c of currentByName.values()) {
278
316
  if (!desiredNames.has(c.table)) dropTables.push({ kind: 'dropTable', table: c.table });
279
317
  }
280
318
 
281
319
  const e = diffEnums(desired.enums ?? [], current.enums ?? []);
282
320
 
283
321
  // Enum types come first (a table or column may reference one); value adds before the
284
- // columns that use them. Renames run before adds (a rename frees a name an add may
285
- // reuse) and before alters (which target the new name). Enum drops sit with the other
286
- // removals; notices are comments emitted last, after the DDL.
322
+ // columns that use them. Table renames run before creates (a rename frees a name a
323
+ // create may reuse) and before every per-table change (which all target the new name).
324
+ // Column renames run before adds and alters for the same reason. Enum drops sit with
325
+ // the other removals; notices are comments — emitted last, after the DDL.
287
326
  return [
288
327
  ...e.creates, ...e.adds,
328
+ ...renameTables,
289
329
  ...creates, ...renameColumns, ...addColumns, ...alters,
290
330
  ...dropConstraints, ...addConstraints, ...createIndexes,
291
331
  ...dropColumns, ...dropIndexes, ...dropTables,
@@ -613,6 +653,13 @@ function emitOne(change: SchemaChange, opts: EmitOptions): string {
613
653
  return `-- NOTICE: primary key on ${change.table} changed (${change.from.join(', ')} → ${change.to.join(', ')}); dropping a primary key needs its constraint name — regenerate this change manually.`;
614
654
  case 'renameColumn':
615
655
  return `ALTER TABLE ${change.table} RENAME COLUMN ${quote(change.from)} TO ${quote(change.to)};`;
656
+ case 'renameTable':
657
+ // RENAME TO takes a bare name — the table stays in its schema.
658
+ return `ALTER TABLE ${change.from} RENAME TO ${quote(change.to.split('.').pop()!)};`;
659
+ case 'renameTableSatisfied':
660
+ return `-- NOTICE: the renamedFrom marker on table ${change.table} is satisfied — the rename from "${change.from}" is applied, the marker is now inert and can be removed.`;
661
+ case 'renameTableSourceMissing':
662
+ return `-- NOTICE: table ${change.table} declares renamedFrom "${change.from}", but the database has neither — created fresh; remove the marker once every environment is past it.`;
616
663
  case 'renameSatisfied':
617
664
  return `-- NOTICE: the renamedFrom marker on column ${quote(change.column)} (${change.table}) is satisfied — the rename from "${change.from}" is applied, the marker is now inert and can be removed.`;
618
665
  case 'renameSourceMissing':