@atscript/db 0.1.161 → 0.1.162

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 (63) hide show
  1. package/dist/agg.cjs +1 -1
  2. package/dist/agg.d.cts +1 -1
  3. package/dist/agg.d.mts +1 -1
  4. package/dist/agg.mjs +1 -1
  5. package/dist/{aggregate-fns-B_sgl4mB.mjs → aggregate-fns-DsS9UQVB.mjs} +1 -1
  6. package/dist/{aggregate-fns-DFtKAv7Q.cjs → aggregate-fns-DupYzFD7.cjs} +1 -1
  7. package/dist/{buckets-8mG67Mtw.d.mts → buckets-DUTcrT4Z.d.mts} +475 -57
  8. package/dist/{buckets-B4T_alUe.d.cts → buckets-_iZV39Z3.d.cts} +475 -57
  9. package/dist/{column-diff-DpeUbhEI.cjs → column-diff-CZsMGQ_w.cjs} +846 -232
  10. package/dist/{column-diff-DR1glykD.mjs → column-diff-pdn53AIj.mjs} +829 -233
  11. package/dist/{validator-B3xlbrWf.d.cts → db-error-B_DRL1E9.d.cts} +18 -89
  12. package/dist/{validator-B3xlbrWf.d.mts → db-error-B_DRL1E9.d.mts} +18 -89
  13. package/dist/{db-error-JcfJqqpk.cjs → db-error-DmF1_xrz.cjs} +13 -0
  14. package/dist/{db-error-eSsQX7rt.mjs → db-error-hraC12B9.mjs} +8 -1
  15. package/dist/{db-validator-plugin-BWy60OvG.d.cts → db-validator-plugin-wRqMYNBI.d.cts} +6 -0
  16. package/dist/{db-validator-plugin-BWy60OvG.d.mts → db-validator-plugin-wRqMYNBI.d.mts} +6 -0
  17. package/dist/{fk-diff-5gX42hBt.d.mts → fk-diff-B5jW68T3.d.cts} +34 -10
  18. package/dist/{fk-diff-BWdQ7kRZ.d.cts → fk-diff-PhVBYohc.d.mts} +34 -10
  19. package/dist/index.cjs +12 -11
  20. package/dist/index.d.cts +24 -21
  21. package/dist/index.d.mts +24 -21
  22. package/dist/index.mjs +9 -12
  23. package/dist/{nested-writer-BOj_c_lI.mjs → nested-writer-ClUX0DB0.mjs} +16 -22
  24. package/dist/{nested-writer-Bl1SZS7x.cjs → nested-writer-yCy6wGuN.cjs} +16 -22
  25. package/dist/{object-CtPYTIvy.d.cts → object-Czia_qD4.d.cts} +7 -1
  26. package/dist/{object-CtPYTIvy.d.mts → object-Czia_qD4.d.mts} +7 -1
  27. package/dist/{object-Djg28csK.cjs → object-GjsuPNqo.cjs} +19 -0
  28. package/dist/{object-TkiJQ-Dp.mjs → object-PQ1J8jri.mjs} +14 -1
  29. package/dist/ops.cjs +6 -21
  30. package/dist/ops.mjs +6 -21
  31. package/dist/plugin.cjs +263 -323
  32. package/dist/plugin.mjs +163 -223
  33. package/dist/rel.cjs +2 -2
  34. package/dist/rel.d.cts +3 -3
  35. package/dist/rel.d.mts +3 -3
  36. package/dist/rel.mjs +2 -2
  37. package/dist/{relation-helpers-17_2f5hX.d.mts → relation-helpers-BAEkP4bH.d.cts} +1 -1
  38. package/dist/{relation-helpers-BLBrLQYi.d.cts → relation-helpers-BSYwxOSA.d.mts} +1 -1
  39. package/dist/{relation-loader-DeulLrAw.cjs → relation-loader-DujL_Mh9.cjs} +1 -1
  40. package/dist/{relation-loader-CNBMPfRr.mjs → relation-loader-Qv3LrQ42.mjs} +1 -1
  41. package/dist/{search-fields-CE8cmj9X.mjs → search-fields-0U0FAFGj.mjs} +47 -1
  42. package/dist/{search-fields-CT_UZ9fg.cjs → search-fields-Bi1n-pB1.cjs} +64 -0
  43. package/dist/search-fields-CHqqcC3W.d.cts +30 -0
  44. package/dist/search-fields-CHqqcC3W.d.mts +30 -0
  45. package/dist/shared.cjs +42 -26
  46. package/dist/shared.d.cts +98 -4
  47. package/dist/shared.d.mts +98 -4
  48. package/dist/shared.mjs +4 -2
  49. package/dist/sync.cjs +228 -65
  50. package/dist/sync.d.cts +41 -5
  51. package/dist/sync.d.mts +41 -5
  52. package/dist/sync.mjs +228 -66
  53. package/dist/validator-BcBtg8yW.d.cts +88 -0
  54. package/dist/validator-BcBtg8yW.d.mts +88 -0
  55. package/dist/{validator-B4YDLUkj.cjs → validator-DK9Ijpo7.cjs} +3 -2
  56. package/dist/{validator-B3fPJjS1.mjs → validator-DuDWsIdQ.mjs} +3 -2
  57. package/dist/validator.cjs +3 -3
  58. package/dist/validator.d.cts +4 -3
  59. package/dist/validator.d.mts +4 -3
  60. package/dist/validator.mjs +3 -3
  61. package/dist/{validation-utils-CtLNlBtB.cjs → view-validation-D50KWU7O.cjs} +452 -18
  62. package/dist/{validation-utils-BGjqc2jU.mjs → view-validation-QbqqXEAM.mjs} +364 -20
  63. package/package.json +6 -6
package/dist/sync.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { At as TPrimaryKeyChange, Jn as TGenericLogger, Lt as TTableOptionDiff, U as TDbFieldMeta, et as TDbObjectKind, gt as TEnsureTableOptions, jt as TReferencingForeignKey, mt as TDerivedChangeReason, nn as DbSpace, vt as TExistingForeignKey, yt as TExistingTableOption } from "./buckets-8mG67Mtw.mjs";
2
- import { _ as computeTableSnapshot, a as hasForeignKeyChanges, b as snapshotToExistingTableOptions, c as TFieldSnapshot, d as TViewColumnSnapshot, f as TViewJoinSnapshot, g as computeTableHash, h as computeSchemaHash, i as fkKey, l as TForeignKeySnapshot, m as canonicalizeQueryNode, n as computeForeignKeyDiff, o as computeColumnDiff, p as TViewSnapshot, t as TForeignKeyDiff, u as TTableSnapshot, v as computeViewSnapshot, y as snapshotToExistingColumns } from "./fk-diff-5gX42hBt.mjs";
1
+ import { Ct as TEnsureTableOptions, Et as TExistingTableOption, G as TDbFieldMeta, Pt as TIndexChange, Tt as TExistingForeignKey, Vt as TReferencingForeignKey, at as TDbObjectKind, cr as TGenericLogger, gn as DbSpace, qt as TTableOptionDiff, xt as TDerivedChangeReason, zt as TPrimaryKeyChange } from "./buckets-DUTcrT4Z.mjs";
2
+ import { S as snapshotToExistingTableOptions, _ as computeSchemaHash, a as hasForeignKeyChanges, b as computeViewSnapshot, d as TForeignKeySnapshot, f as TTableSnapshot, g as canonicalizeQueryNode, h as TViewSnapshot, i as fkKey, l as sameDefaultValue, m as TViewJoinSnapshot, n as computeForeignKeyDiff, o as TColumnDiffOptions, p as TViewColumnSnapshot, s as computeColumnDiff, t as TForeignKeyDiff, u as TFieldSnapshot, v as computeTableHash, x as snapshotToExistingColumns, y as computeTableSnapshot } from "./fk-diff-PhVBYohc.mjs";
3
3
  import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
4
4
 
5
5
  //#region src/schema/sync-entry.d.ts
@@ -150,6 +150,23 @@ interface TSyncEntryInit {
150
150
  * @since 0.1.141
151
151
  */
152
152
  cascadeFrom?: string[];
153
+ /**
154
+ * `true` on a managed view whose own definition changed since the last
155
+ * sync — the reason it is dropped and recreated. Printed as
156
+ * `· definition changed`.
157
+ * @since 0.1.162
158
+ */
159
+ definitionChanged?: boolean;
160
+ /**
161
+ * Managed indexes created, dropped or rebuilt (definition drift). On a
162
+ * plan entry: what the run's index sync would do to the live table as it
163
+ * is now (before the entry's column changes; not planned for a table the
164
+ * run creates, recreates or renames). On a run entry: what it did — not
165
+ * reported for a created or recreated table. Either marks the entry
166
+ * `alter`.
167
+ * @since 0.1.162
168
+ */
169
+ indexChanges?: TIndexChange[];
153
170
  }
154
171
  declare class SyncEntry {
155
172
  readonly name: string;
@@ -223,6 +240,10 @@ declare class SyncEntry {
223
240
  readonly refused: boolean;
224
241
  /** @since 0.1.141 — see {@link TSyncEntryInit.cascadeFrom}. */
225
242
  readonly cascadeFrom: string[];
243
+ /** @since 0.1.162 — see {@link TSyncEntryInit.definitionChanged}. */
244
+ readonly definitionChanged: boolean;
245
+ /** @since 0.1.162 — see {@link TSyncEntryInit.indexChanges}. */
246
+ readonly indexChanges: TIndexChange[];
226
247
  constructor(init: TSyncEntryInit);
227
248
  /**
228
249
  * The init object this entry was built from — lets callers derive a
@@ -259,8 +280,9 @@ declare class SyncEntry {
259
280
  /** `! PK (id) → (code) — rebuild (table is empty)` / `— rebuild (rows satisfy the new key)` / `— skipped (safe mode)` */
260
281
  private printPkChange;
261
282
  /**
262
- * `! col: t1 → t2 — drop` (plan), or `! type col (t1 → t2) — skipped (safe
263
- * mode)` in plan and result when the `'drop'` recreate was skipped.
283
+ * `! col: t1 → t2 — drop` (plan), `~ col: t1 → t2 — type changed` (result),
284
+ * or `! type col (t1 → t2) — skipped (safe mode)` in plan and result when
285
+ * the `'drop'` recreate was skipped.
264
286
  */
265
287
  private printTypeChanges;
266
288
  /**
@@ -278,6 +300,12 @@ declare class SyncEntry {
278
300
  * change. Both: `! option k: a → b — skipped (safe mode)` when pending.
279
301
  */
280
302
  private printOptionChanges;
303
+ /** `+ FK(a) → t — add` / `- … — remove` / `~ … — details` (plan); `added` / `removed` (result). */
304
+ private printForeignKeys;
305
+ /** `+ index name — add` / `- … — drop` / `~ … — rebuild` (plan); `added` / `dropped` / `rebuilt` (result). */
306
+ private printIndexChanges;
307
+ /** `· definition changed` — why a managed view is recreated. */
308
+ private printDefinitionChanged;
281
309
  /** `· after: a, b` (plan only — the executor already runs in this order). */
282
310
  private printDependsOn;
283
311
  /** `· upstream view "x" recreated` — why an unchanged view is recreated. */
@@ -423,6 +451,14 @@ declare class SchemaSync {
423
451
  * `safe` — safe mode skips the key rebuild, so its plan rule does not apply.
424
452
  */
425
453
  private discoverTable;
454
+ /**
455
+ * Folds the index / foreign-key work on each live table as it is now
456
+ * (`planIndexes` / `planForeignKeys`) into its plan entry. Read only when
457
+ * plan entries are reported (`plan()`, a refused run) — a run reports what
458
+ * `syncIndexes` / `syncForeignKeys` did.
459
+ */
460
+ private planLiveWork;
461
+ private planTableLiveWork;
426
462
  /**
427
463
  * Pure validation over the discovery facts. Every refusal is a change that
428
464
  * no order of DDL can apply safely; the messages are user-facing.
@@ -566,4 +602,4 @@ declare function syncSchema(space: DbSpace, types: readonly TAtscriptAnnotatedTy
566
602
  */
567
603
  declare function planSchema(space: DbSpace, types: readonly TAtscriptAnnotatedType[], opts?: Pick<TSyncOptions, "force" | "safe">): Promise<TSyncPlan>;
568
604
  //#endregion
569
- export { SchemaSync, SyncEntry, type TDbObjectKind, type TDependencyEdge, type TEnsureTableOptions, type TExistingForeignKey, type TFieldSnapshot, type TForeignKeyDiff, type TForeignKeySnapshot, type TPrimaryKeyChange, type TReferencingForeignKey, type TSyncColors, type TSyncDerivedChange, type TSyncEntryInit, type TSyncEntryStatus, type TSyncOptions, type TSyncPlan, type TSyncResult, type TSyncSkippedWork, type TTableSnapshot, type TViewColumnSnapshot, type TViewJoinSnapshot, type TViewSnapshot, canonicalizeQueryNode, computeColumnDiff, computeForeignKeyDiff, computeSchemaHash, computeTableHash, computeTableOptionDiff, computeTableSnapshot, computeViewSnapshot, fkKey, hasForeignKeyChanges, planSchema, readStoredSnapshot, snapshotToExistingColumns, snapshotToExistingTableOptions, syncSchema, topoOrder };
605
+ export { SchemaSync, SyncEntry, type TColumnDiffOptions, type TDbObjectKind, type TDependencyEdge, type TEnsureTableOptions, type TExistingForeignKey, type TFieldSnapshot, type TForeignKeyDiff, type TForeignKeySnapshot, type TPrimaryKeyChange, type TReferencingForeignKey, type TSyncColors, type TSyncDerivedChange, type TSyncEntryInit, type TSyncEntryStatus, type TSyncOptions, type TSyncPlan, type TSyncResult, type TSyncSkippedWork, type TTableSnapshot, type TViewColumnSnapshot, type TViewJoinSnapshot, type TViewSnapshot, canonicalizeQueryNode, computeColumnDiff, computeForeignKeyDiff, computeSchemaHash, computeTableHash, computeTableOptionDiff, computeTableSnapshot, computeViewSnapshot, fkKey, hasForeignKeyChanges, planSchema, readStoredSnapshot, sameDefaultValue, snapshotToExistingColumns, snapshotToExistingTableOptions, syncSchema, topoOrder };
package/dist/sync.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { r as DbError } from "./db-error-eSsQX7rt.mjs";
2
- import { a as computeTableHash, c as serializeDefaultValue, d as viewSnapshotSources, f as computeForeignKeyDiff, ft as relationalColumnName, h as hasForeignKeyChanges, i as computeSchemaHash, j as resolveDesignType, l as snapshotToExistingColumns, m as fkKey, o as computeTableSnapshot, p as fkColumns, pt as aliasTargetOf, r as canonicalizeQueryNode, s as computeViewSnapshot, t as computeColumnDiff, u as snapshotToExistingTableOptions, yt as NoopLogger } from "./column-diff-DR1glykD.mjs";
3
- import { f as unionValueMembers } from "./validator-B3fPJjS1.mjs";
1
+ import { r as DbError } from "./db-error-hraC12B9.mjs";
2
+ import { A as hasForeignKeyChanges, D as computeForeignKeyDiff, O as fkColumns, P as resolveDesignType, St as NoopLogger, a as computeSchemaHash, c as computeViewSnapshot, d as snapshotToExistingTableOptions, f as viewSnapshotSources, gt as aliasTargetOf, ht as relationalColumnName, i as canonicalizeQueryNode, k as fkKey, l as serializeDefaultValue, o as computeTableHash, r as sameDefaultValue, s as computeTableSnapshot, t as computeColumnDiff, u as snapshotToExistingColumns } from "./column-diff-pdn53AIj.mjs";
3
+ import { f as unionValueMembers } from "./validator-DuDWsIdQ.mjs";
4
4
  //#region src/schema/json-column-copy.ts
5
5
  const NONE = {
6
6
  copies: [],
@@ -505,6 +505,24 @@ async function readStoredSnapshot(space, tableName, _asView) {
505
505
  }
506
506
  //#endregion
507
507
  //#region src/schema/sync-entry.ts
508
+ /** How each index change prints: its mark, color and past-tense word. */
509
+ const INDEX_CHANGE_STYLE = {
510
+ add: {
511
+ mark: "+",
512
+ color: "green",
513
+ done: "added"
514
+ },
515
+ drop: {
516
+ mark: "-",
517
+ color: "red",
518
+ done: "dropped"
519
+ },
520
+ rebuild: {
521
+ mark: "~",
522
+ color: "yellow",
523
+ done: "rebuilt"
524
+ }
525
+ };
508
526
  const noColor = {
509
527
  green: (s) => s,
510
528
  red: (s) => s,
@@ -559,6 +577,10 @@ var SyncEntry = class SyncEntry {
559
577
  refused;
560
578
  /** @since 0.1.141 — see {@link TSyncEntryInit.cascadeFrom}. */
561
579
  cascadeFrom;
580
+ /** @since 0.1.162 — see {@link TSyncEntryInit.definitionChanged}. */
581
+ definitionChanged;
582
+ /** @since 0.1.162 — see {@link TSyncEntryInit.indexChanges}. */
583
+ indexChanges;
562
584
  constructor(init) {
563
585
  this.name = init.name;
564
586
  this.viewType = init.viewType;
@@ -589,6 +611,8 @@ var SyncEntry = class SyncEntry {
589
611
  this.dropGroup = init.dropGroup;
590
612
  this.refused = init.refused ?? false;
591
613
  this.cascadeFrom = init.cascadeFrom ?? [];
614
+ this.definitionChanged = init.definitionChanged ?? false;
615
+ this.indexChanges = init.indexChanges ?? [];
592
616
  }
593
617
  /**
594
618
  * The init object this entry was built from — lets callers derive a
@@ -659,11 +683,13 @@ var SyncEntry = class SyncEntry {
659
683
  return mode === "plan" ? [` ${c.red(`! ${cols} — rebuild (${pk.populated ? "rows satisfy the new key" : "table is empty"})`)}`] : [` ${c.yellow(`~ ${cols} — rebuilt`)}`];
660
684
  }
661
685
  /**
662
- * `! col: t1 → t2 — drop` (plan), or `! type col (t1 → t2) — skipped (safe
663
- * mode)` in plan and result when the `'drop'` recreate was skipped.
686
+ * `! col: t1 → t2 — drop` (plan), `~ col: t1 → t2 — type changed` (result),
687
+ * or `! type col (t1 → t2) — skipped (safe mode)` in plan and result when
688
+ * the `'drop'` recreate was skipped.
664
689
  */
665
- printTypeChanges(c) {
690
+ printTypeChanges(c, mode) {
666
691
  if (this.skipped.includes("recreate")) return this.typeChanges.map((tc) => ` ${c.yellow(`! type ${tc.column} (${tc.fromType} → ${tc.toType}) — skipped (safe mode)`)}`);
692
+ if (mode === "result") return this.typeChanges.map((tc) => ` ${c.yellow(`~ ${tc.column}: ${tc.fromType} → ${tc.toType} — type changed`)}`);
667
693
  return this.typeChanges.map((tc) => {
668
694
  const action = this.syncMethod ? ` — ${this.syncMethod}` : " — requires migration";
669
695
  return ` ${c.red(`! ${tc.column}: ${tc.fromType} → ${tc.toType}${action}`)}`;
@@ -707,6 +733,26 @@ var SyncEntry = class SyncEntry {
707
733
  return ` ${tag} ${c.cyan(`option ${oc.key}`)}: ${oc.oldValue} → ${oc.newValue}${action}`;
708
734
  });
709
735
  }
736
+ /** `+ FK(a) → t — add` / `- … — remove` / `~ … — details` (plan); `added` / `removed` (result). */
737
+ printForeignKeys(c, mode) {
738
+ const [add, remove] = mode === "plan" ? ["add", "remove"] : ["added", "removed"];
739
+ return [
740
+ ...this.fkAdded.map((fk) => ` ${c.green(`+ FK(${fk.fields.join(",")}) → ${fk.targetTable} — ${add}`)}`),
741
+ ...this.fkRemoved.map((fk) => ` ${c.red(`- FK(${fk.fields.join(",")})${fk.targetTable ? ` → ${fk.targetTable}` : ""} — ${remove}`)}`),
742
+ ...this.fkChanged.map((fk) => ` ${c.yellow(`~ FK(${fk.fields.join(",")}) → ${fk.targetTable} — ${fk.details}`)}`)
743
+ ];
744
+ }
745
+ /** `+ index name — add` / `- … — drop` / `~ … — rebuild` (plan); `added` / `dropped` / `rebuilt` (result). */
746
+ printIndexChanges(c, mode) {
747
+ return this.indexChanges.map((ic) => {
748
+ const { mark, color, done } = INDEX_CHANGE_STYLE[ic.action];
749
+ return ` ${c[color](`${mark} index ${ic.name} — ${mode === "plan" ? ic.action : done}`)}`;
750
+ });
751
+ }
752
+ /** `· definition changed` — why a managed view is recreated. */
753
+ printDefinitionChanged(c) {
754
+ return this.definitionChanged ? [` ${c.dim("· definition changed")}`] : [];
755
+ }
710
756
  /** `· after: a, b` (plan only — the executor already runs in this order). */
711
757
  printDependsOn(c) {
712
758
  if (this.dependsOn.length === 0) return [];
@@ -745,15 +791,15 @@ var SyncEntry = class SyncEntry {
745
791
  ...this.columnsToAdd.map((col) => ` ${c.green(`+ ${col.physicalName} (${col.designType})${col.derived ? " derived" : ""} — add`)}`),
746
792
  ...this.columnsToRename.map((r) => ` ${c.yellow(`~ ${r.from} → ${r.to} — rename`)}`),
747
793
  ...this.printJsonMigration(c, "plan"),
748
- ...this.printTypeChanges(c),
794
+ ...this.printTypeChanges(c, "plan"),
749
795
  ...this.printNullableDefaults(c),
750
796
  ...this.printDerivedChanges(c, "plan"),
751
797
  ...this.printPkChange(c, "plan"),
752
798
  ...this.columnsToDrop.map((col) => ` ${c.red(`- ${col} — drop`)}`),
753
799
  ...this.printOptionChanges(c, "plan"),
754
- ...this.fkAdded.map((fk) => ` ${c.green(`+ FK(${fk.fields.join(",")}) → ${fk.targetTable} — add`)}`),
755
- ...this.fkRemoved.map((fk) => ` ${c.red(`- FK(${fk.fields.join(",")}) → ${fk.targetTable} — remove`)}`),
756
- ...this.fkChanged.map((fk) => ` ${c.yellow(`~ FK(${fk.fields.join(",")}) → ${fk.targetTable} — ${fk.details}`)}`),
800
+ ...this.printForeignKeys(c, "plan"),
801
+ ...this.printIndexChanges(c, "plan"),
802
+ ...this.printDefinitionChanged(c),
757
803
  ...this.printCascade(c),
758
804
  ...this.printDependsOn(c),
759
805
  ""
@@ -773,7 +819,7 @@ var SyncEntry = class SyncEntry {
773
819
  ...this.columnsAdded.map((col) => ` ${c.green(`+ ${col} — added`)}`),
774
820
  ""
775
821
  ];
776
- if (this.columnsAdded.length > 0 || this.columnsRenamed.length > 0 || this.columnsDropped.length > 0 || this.optionChanges.length > 0 || this.pkChange !== void 0 || this.derivedChanges.length > 0 || this.jsonCopies.length > 0 || this.jsonified.length > 0 || this.skipped.length > 0 || this.recreated || this.renamedFrom) {
822
+ if (this.columnsAdded.length > 0 || this.columnsRenamed.length > 0 || this.columnsDropped.length > 0 || this.optionChanges.length > 0 || this.pkChange !== void 0 || this.derivedChanges.length > 0 || this.jsonCopies.length > 0 || this.jsonified.length > 0 || this.typeChanges.length > 0 || this.nullableChanges.length > 0 || this.defaultChanges.length > 0 || this.fkAdded.length > 0 || this.fkRemoved.length > 0 || this.fkChanged.length > 0 || this.indexChanges.length > 0 || this.skipped.length > 0 || this.recreated || this.renamedFrom || this.status === "alter") {
777
823
  const rlabel = this.recreated ? "recreated" : "altered";
778
824
  const renameInfo = this.renamedFrom ? ` ${c.yellow(`(renamed from ${this.renamedFrom})`)}` : "";
779
825
  return [
@@ -782,11 +828,14 @@ var SyncEntry = class SyncEntry {
782
828
  ...this.columnsRenamed.map((col) => ` ${c.yellow(`~ ${col} — renamed`)}`),
783
829
  ...this.printJsonMigration(c, "result"),
784
830
  ...this.printPkChange(c, "result"),
785
- ...this.printTypeChanges(c),
831
+ ...this.printTypeChanges(c, "result"),
786
832
  ...this.printNullableDefaults(c),
787
833
  ...this.printDerivedChanges(c, "result"),
788
834
  ...this.columnsDropped.map((col) => ` ${c.red(`- ${col} — dropped`)}`),
789
835
  ...this.printOptionChanges(c, "result"),
836
+ ...this.printForeignKeys(c, "result"),
837
+ ...this.printIndexChanges(c, "result"),
838
+ ...this.printDefinitionChanged(c),
790
839
  ...this.printCascade(c),
791
840
  ""
792
841
  ];
@@ -920,10 +969,7 @@ async function executeSyncTable(facts, safe, deps, exec) {
920
969
  await adapter.ensureTable(ensureOpts);
921
970
  init.status = "create";
922
971
  } else if (existing.length > 0) {
923
- let diff = facts.diff ?? computeColumnDiff(readable.columnDescriptors, existing, adapter.typeMapper?.bind(adapter), {
924
- snapshot: storedSnapshot,
925
- nativeDefaultFns: adapter.nativeDefaultFns()
926
- });
972
+ let diff = facts.diff ?? computeColumnDiff(readable.columnDescriptors, existing, adapter.typeMapper?.bind(adapter), columnDiffOptions(adapter, storedSnapshot));
927
973
  const migration = facts.jsonMigration ?? planJsonColumnMigration(readable, diff, storedSnapshot);
928
974
  if (migration.errors.length > 0) throw new Error(migration.errors.join(" "));
929
975
  if (migration.copies.length > 0 || migration.jsonify.length > 0) {
@@ -960,10 +1006,7 @@ async function executeSyncTable(facts, safe, deps, exec) {
960
1006
  const existed = adapter.tableExists ? await adapter.tableExists() : false;
961
1007
  await adapter.ensureTable(ensureOpts);
962
1008
  if (!existed) init.status = "create";
963
- } else await applyColumnDiff(adapter, readable, facts.diff ?? computeColumnDiff(readable.columnDescriptors, snapshotToExistingColumns(storedSnapshot, readable), deps.resolveTypeMapper(adapter), {
964
- snapshot: storedSnapshot,
965
- nativeDefaultFns: adapter.nativeDefaultFns()
966
- }), init, safe, deps.logger, ensureOpts);
1009
+ } else await applyColumnDiff(adapter, readable, facts.diff ?? computeColumnDiff(readable.columnDescriptors, snapshotToExistingColumns(storedSnapshot, readable), deps.resolveTypeMapper(adapter), columnDiffOptions(adapter, storedSnapshot)), init, safe, deps.logger, ensureOpts);
967
1010
  else {
968
1011
  const existed = adapter.tableExists ? await adapter.tableExists() : true;
969
1012
  if (!init.recreated) {
@@ -971,6 +1014,10 @@ async function executeSyncTable(facts, safe, deps, exec) {
971
1014
  if (!existed) init.status = "create";
972
1015
  }
973
1016
  }
1017
+ if (hasFkChanges && fkDiff && init.status !== "create" && init.status !== "error") {
1018
+ Object.assign(init, describeForeignKeyChanges(fkDiff));
1019
+ init.status = "alter";
1020
+ }
974
1021
  phase = "Table option sync";
975
1022
  const optionDiff = facts.optionDiff;
976
1023
  if (init.status !== "create" && !init.recreated && init.status !== "error" && optionDiff && optionDiff.changed.length > 0) {
@@ -1005,8 +1052,15 @@ async function executeSyncTable(facts, safe, deps, exec) {
1005
1052
  if (init.pkChange && facts.populated === true) init.pkChange.populated = true;
1006
1053
  if (init.status === "error") return new SyncEntry(init);
1007
1054
  phase = "Index/FK sync";
1008
- await adapter.syncIndexes(keptIndexes(init));
1009
- if (adapter.syncForeignKeys && !exec.deferForeignKeysTo) await adapter.syncForeignKeys();
1055
+ const indexChanges = await adapter.syncIndexes(keptIndexes(init));
1056
+ if (Array.isArray(indexChanges) && indexChanges.length > 0 && init.status !== "create" && !init.recreated) {
1057
+ init.indexChanges = indexChanges;
1058
+ init.status = "alter";
1059
+ }
1060
+ if (adapter.syncForeignKeys && !exec.deferForeignKeysTo) {
1061
+ const fkChanges = await adapter.syncForeignKeys();
1062
+ if (init.status !== "create" && !init.recreated && mergeLiveForeignKeyChanges(init, fkChanges)) init.status = "alter";
1063
+ }
1010
1064
  if (adapter.afterSyncTable) await adapter.afterSyncTable();
1011
1065
  } catch (error) {
1012
1066
  const dropped = droppedFks.length > 0 ? ` Dropped foreign keys before the failure: ${droppedFks.join(", ")} — the ones still in the model are re-added by the next run's syncForeignKeys.` : "";
@@ -1035,8 +1089,13 @@ async function executeDeferredForeignKeys(readable, entry, deps) {
1035
1089
  const adapter = readable.dbAdapter;
1036
1090
  if (!adapter.syncForeignKeys || entry.status === "error") return entry;
1037
1091
  try {
1038
- await adapter.syncForeignKeys();
1039
- return entry;
1092
+ const fkChanges = await adapter.syncForeignKeys();
1093
+ if (entry.status === "create" || entry.recreated) return entry;
1094
+ const init = entry.toInit();
1095
+ return mergeLiveForeignKeyChanges(init, fkChanges) ? new SyncEntry({
1096
+ ...init,
1097
+ status: "alter"
1098
+ }) : entry;
1040
1099
  } catch (error) {
1041
1100
  const msg = `FK sync failed on ${readable.tableName}: ${error.message}`;
1042
1101
  deps.logger.error?.(`[schema-sync] ${msg}`);
@@ -1082,6 +1141,14 @@ function describeDerivedChanges(diff) {
1082
1141
  derived: dc.field.derived !== void 0
1083
1142
  }));
1084
1143
  }
1144
+ /** The {@link computeColumnDiff} options for a table of `adapter`. */
1145
+ function columnDiffOptions(adapter, snapshot) {
1146
+ return {
1147
+ snapshot,
1148
+ nativeDefaultFns: adapter.nativeDefaultFns(),
1149
+ reportedDefault: adapter.reportedDefault?.bind(adapter)
1150
+ };
1151
+ }
1085
1152
  /** The plan-shaped summary of a diff's type changes (`entry.typeChanges`). */
1086
1153
  function describeTypeChanges(diff) {
1087
1154
  return diff.typeChanged.map((tc) => ({
@@ -1120,6 +1187,28 @@ function markSkipped(init, kind) {
1120
1187
  init.skipped = [...init.skipped ?? [], kind];
1121
1188
  }
1122
1189
  /**
1190
+ * Whether the run (without safe mode) recreates this existing table — an FK
1191
+ * change on an adapter without `syncForeignKeys`, a `'drop'` / `'recreate'`
1192
+ * type change, nullable / default DDL through `recreateTable`, or a key
1193
+ * rebuild without `rebuildPrimaryKey`. The plan predicts no index work for
1194
+ * such a table: the recreate rebuilds every index.
1195
+ */
1196
+ function runRecreatesTable(facts, safe = false) {
1197
+ const { readable, fkDiff } = facts;
1198
+ const adapter = readable.dbAdapter;
1199
+ if (!adapter.recreateTable) return false;
1200
+ if (fkDiff && hasForeignKeyChanges(fkDiff) && !adapter.syncForeignKeys) return true;
1201
+ const diff = facts.diff;
1202
+ if (!diff) return false;
1203
+ if (diff.typeChanged.length > 0) {
1204
+ const strategy = typeChangeStrategy(readable, diff);
1205
+ if (strategy === "recreate" || strategy === "drop" && !safe) return true;
1206
+ }
1207
+ if (safe) return false;
1208
+ if ((diff.nullableChanged.length > 0 || diff.defaultChanged.length > 0) && !adapter.supportsColumnModify && !!adapter.getExistingColumns) return true;
1209
+ return !!diff.primaryKeyChanged && !adapter.rebuildPrimaryKey;
1210
+ }
1211
+ /**
1123
1212
  * Whether nullable/default changes need DDL on this adapter: in-place modify,
1124
1213
  * or a recreate on a live-introspected one (SQLite). Snapshot-based adapters
1125
1214
  * (MongoDB) have no column constraint to change — they only update the
@@ -1129,6 +1218,20 @@ function needsDdlForNullableDefaults(adapter) {
1129
1218
  return !!adapter.supportsColumnModify && !!adapter.syncColumns || !!adapter.recreateTable && !!adapter.getExistingColumns;
1130
1219
  }
1131
1220
  /**
1221
+ * The `syncIndexes` options the run will pass for this table — what the plan
1222
+ * passes `planIndexes`, so both report the same index work.
1223
+ */
1224
+ function plannedIndexOptions(facts, safe) {
1225
+ if (!safe) return;
1226
+ const diff = afterJsonCopy(facts);
1227
+ return keptIndexes({
1228
+ name: facts.name,
1229
+ status: "alter",
1230
+ skipped: safeModeSkips(facts),
1231
+ typeChanges: diff ? describeTypeChanges(diff) : []
1232
+ });
1233
+ }
1234
+ /**
1132
1235
  * The indexes `syncIndexes` keeps as they are because safe mode skipped
1133
1236
  * the change they depend on — a skipped key rebuild, the columns of a
1134
1237
  * skipped type-change recreate (see `TSyncIndexesOptions`).
@@ -1177,6 +1280,68 @@ function describeJsonMigration(migration) {
1177
1280
  jsonified: migration.jsonify.length > 0 ? migration.jsonify : void 0
1178
1281
  };
1179
1282
  }
1283
+ /** Builds a human-readable description of what changed in an FK constraint. */
1284
+ function buildFkChangeDetails(desired, existing) {
1285
+ const parts = [];
1286
+ if (existing.targetTable !== desired.targetTable) parts.push(`retarget ${existing.targetTable} → ${desired.targetTable}`);
1287
+ if ([...existing.targetFields].toSorted().join(",") !== [...desired.targetFields].toSorted().join(",")) parts.push(`fields ${existing.targetFields.join(",")} → ${desired.targetFields.join(",")}`);
1288
+ if ((existing.onDelete ?? void 0) !== (desired.onDelete ?? void 0)) parts.push(`onDelete ${existing.onDelete ?? "noAction"} → ${desired.onDelete ?? "noAction"}`);
1289
+ if ((existing.onUpdate ?? void 0) !== (desired.onUpdate ?? void 0)) parts.push(`onUpdate ${existing.onUpdate ?? "noAction"} → ${desired.onUpdate ?? "noAction"}`);
1290
+ return parts.join(", ");
1291
+ }
1292
+ /**
1293
+ * The plan-shaped summaries of a foreign-key diff (`entry.fkAdded` /
1294
+ * `fkRemoved` / `fkChanged`) — on plan entries and on the run entries that
1295
+ * applied them.
1296
+ */
1297
+ function describeForeignKeyChanges(fkDiff) {
1298
+ return {
1299
+ fkAdded: fkDiff.added.map((fk) => ({
1300
+ fields: fkColumns(fk).fields,
1301
+ targetTable: fk.targetTable
1302
+ })),
1303
+ fkRemoved: fkDiff.removed.map((fk) => ({
1304
+ fields: fk.fields,
1305
+ targetTable: fk.targetTable
1306
+ })),
1307
+ fkChanged: fkDiff.changed.map((fk) => ({
1308
+ fields: fkColumns(fk.desired).fields,
1309
+ targetTable: fk.desired.targetTable,
1310
+ details: buildFkChangeDetails({
1311
+ ...fk.desired,
1312
+ targetFields: fkColumns(fk.desired).targetFields
1313
+ }, fk.existing)
1314
+ }))
1315
+ };
1316
+ }
1317
+ /**
1318
+ * Folds the live FK changes `syncForeignKeys` / `planForeignKeys` report
1319
+ * into the entry: those the snapshot-based FK diff did not already name
1320
+ * (a constraint dropped or added by hand) join `fkAdded` / `fkRemoved`.
1321
+ * `true` when any was added.
1322
+ */
1323
+ function mergeLiveForeignKeyChanges(init, changes) {
1324
+ if (!changes || changes.length === 0) return false;
1325
+ const known = new Set([
1326
+ ...init.fkAdded ?? [],
1327
+ ...init.fkRemoved ?? [],
1328
+ ...init.fkChanged ?? []
1329
+ ].map((fk) => fkKey(fk.fields)));
1330
+ let merged = false;
1331
+ for (const change of changes) {
1332
+ const key = fkKey(change.fields);
1333
+ if (known.has(key)) continue;
1334
+ known.add(key);
1335
+ const fk = {
1336
+ fields: change.fields,
1337
+ targetTable: change.targetTable ?? ""
1338
+ };
1339
+ if (change.action === "add") init.fkAdded = [...init.fkAdded ?? [], fk];
1340
+ else init.fkRemoved = [...init.fkRemoved ?? [], fk];
1341
+ merged = true;
1342
+ }
1343
+ return merged;
1344
+ }
1180
1345
  /** The plan-shaped nullable / default change summaries of a diff. */
1181
1346
  function describeNullableDefaults(diff) {
1182
1347
  return {
@@ -1241,11 +1406,12 @@ async function applyColumnDiff(adapter, readable, diff, init, safe, logger, ensu
1241
1406
  let needsSyncColumns = false;
1242
1407
  if (diff.typeChanged.length > 0 && init.status !== "error") {
1243
1408
  const strategy = typeChangeStrategy(readable, diff);
1409
+ const typeChanges = describeTypeChanges(diff);
1410
+ if (strategy !== "none") init.typeChanges = typeChanges;
1244
1411
  if (strategy === "drop") if (safe) {
1245
- init.typeChanges = describeTypeChanges(diff);
1246
1412
  markSkipped(init, "recreate");
1247
1413
  init.status = "alter";
1248
- const cols = init.typeChanges.map((tc) => `${tc.column}: ${tc.fromType} → ${tc.toType}`).join(", ");
1414
+ const cols = typeChanges.map((tc) => `${tc.column}: ${tc.fromType} → ${tc.toType}`).join(", ");
1249
1415
  logger.warn?.(`[schema-sync] Type change on "${name}" (${cols}) — drop-and-recreate skipped (safe mode)`);
1250
1416
  } else await dropAndRecreate(adapter, init, name, ensureOpts);
1251
1417
  else if (strategy === "recreate") await recreateKeepingRenames(adapter, diff, init);
@@ -1260,12 +1426,15 @@ async function applyColumnDiff(adapter, readable, diff, init, safe, logger, ensu
1260
1426
  }
1261
1427
  }
1262
1428
  if (diff.nullableChanged.length > 0 || diff.defaultChanged.length > 0) {
1263
- if (!safe && !init.recreated && init.status !== "error") if (adapter.supportsColumnModify && adapter.syncColumns) {
1264
- needsSyncColumns = true;
1265
- init.status = "alter";
1266
- } else if (needsDdlForNullableDefaults(adapter)) await recreateKeepingRenames(adapter, diff, init);
1267
- else init.status = "alter";
1268
- else if (init.status !== "error") {
1429
+ if (!safe && !init.recreated && init.status !== "error") {
1430
+ if (adapter.supportsColumnModify && adapter.syncColumns) {
1431
+ needsSyncColumns = true;
1432
+ init.status = "alter";
1433
+ } else if (needsDdlForNullableDefaults(adapter)) await recreateKeepingRenames(adapter, diff, init);
1434
+ else init.status = "alter";
1435
+ Object.assign(init, describeNullableDefaults(diff));
1436
+ } else if (init.status !== "error") {
1437
+ if (!safe || !needsDdlForNullableDefaults(adapter) || init.recreated) Object.assign(init, describeNullableDefaults(diff));
1269
1438
  if (safe && !init.recreated && needsDdlForNullableDefaults(adapter)) {
1270
1439
  Object.assign(init, describeNullableDefaults(diff));
1271
1440
  markSkipped(init, "nullable-defaults");
@@ -1360,15 +1529,6 @@ async function applyColumnDiff(adapter, readable, diff, init, safe, logger, ensu
1360
1529
  function retain(group, ctx) {
1361
1530
  ctx.retained.push(...group.map((name) => ctx.d.removedByName.get(name)));
1362
1531
  }
1363
- /** Builds a human-readable description of what changed in an FK constraint. */
1364
- function buildFkChangeDetails(desired, existing) {
1365
- const parts = [];
1366
- if (existing.targetTable !== desired.targetTable) parts.push(`retarget ${existing.targetTable} → ${desired.targetTable}`);
1367
- if ([...existing.targetFields].toSorted().join(",") !== [...desired.targetFields].toSorted().join(",")) parts.push(`fields ${existing.targetFields.join(",")} → ${desired.targetFields.join(",")}`);
1368
- if ((existing.onDelete ?? void 0) !== (desired.onDelete ?? void 0)) parts.push(`onDelete ${existing.onDelete ?? "noAction"} → ${desired.onDelete ?? "noAction"}`);
1369
- if ((existing.onUpdate ?? void 0) !== (desired.onUpdate ?? void 0)) parts.push(`onUpdate ${existing.onUpdate ?? "noAction"} → ${desired.onUpdate ?? "noAction"}`);
1370
- return parts.join(", ");
1371
- }
1372
1532
  function sortedUnique(names) {
1373
1533
  return [...new Set(names)].toSorted();
1374
1534
  }
@@ -1640,6 +1800,7 @@ var SchemaSync = class {
1640
1800
  this.assertLockHeld(heartbeat.getAbortReason);
1641
1801
  const refusals = this.preflight(d, safe);
1642
1802
  if (refusals.size > 0) {
1803
+ await this.planLiveWork(d, safe);
1643
1804
  const result = {
1644
1805
  status: "refused",
1645
1806
  schemaHash: hash,
@@ -1704,6 +1865,7 @@ var SchemaSync = class {
1704
1865
  renamedFrom: plan.isRenamed ? view.renamedFrom : void 0,
1705
1866
  recreated: viewRecreated(plan) || void 0,
1706
1867
  cascadeFrom: plan.upstreamRecreated,
1868
+ definitionChanged: plan.definitionChanged || void 0,
1707
1869
  dependsOn: viewSources.get(view.tableName).filter((n) => tableNames.has(n) || viewNames.has(n) || externalViewNames.has(n))
1708
1870
  }));
1709
1871
  }
@@ -1841,10 +2003,7 @@ var SchemaSync = class {
1841
2003
  init.columnsToAdd = [...readable.columnDescriptors];
1842
2004
  } else if (facts.existing && facts.existing.length > 0) {
1843
2005
  const typeMapper = adapter.typeMapper?.bind(adapter);
1844
- facts.diff = computeColumnDiff(readable.columnDescriptors, facts.existing, typeMapper, {
1845
- snapshot: storedSnapshot,
1846
- nativeDefaultFns: adapter.nativeDefaultFns()
1847
- });
2006
+ facts.diff = computeColumnDiff(readable.columnDescriptors, facts.existing, typeMapper, columnDiffOptions(adapter, storedSnapshot));
1848
2007
  this.populatePlanFromDiff(facts.diff, init, readable, safe);
1849
2008
  const migration = planJsonColumnMigration(readable, facts.diff, storedSnapshot);
1850
2009
  facts.jsonMigration = migration;
@@ -1863,10 +2022,7 @@ var SchemaSync = class {
1863
2022
  }
1864
2023
  } else {
1865
2024
  const existing = snapshotToExistingColumns(storedSnapshot, readable);
1866
- facts.diff = computeColumnDiff(readable.columnDescriptors, existing, this.resolveTypeMapper(adapter), {
1867
- snapshot: storedSnapshot,
1868
- nativeDefaultFns: adapter.nativeDefaultFns()
1869
- });
2025
+ facts.diff = computeColumnDiff(readable.columnDescriptors, existing, this.resolveTypeMapper(adapter), columnDiffOptions(adapter, storedSnapshot));
1870
2026
  this.populatePlanFromDiff(facts.diff, init, readable, safe);
1871
2027
  }
1872
2028
  else if (adapter.tableExists) {
@@ -1884,22 +2040,7 @@ var SchemaSync = class {
1884
2040
  if (init.status !== "create" && init.status !== "error" && facts.fkDiff && hasForeignKeyChanges(facts.fkDiff)) {
1885
2041
  const fkDiff = facts.fkDiff;
1886
2042
  init.status = "alter";
1887
- init.fkAdded = fkDiff.added.map((fk) => ({
1888
- fields: fkColumns(fk).fields,
1889
- targetTable: fk.targetTable
1890
- }));
1891
- init.fkRemoved = fkDiff.removed.map((fk) => ({
1892
- fields: fk.fields,
1893
- targetTable: fk.targetTable
1894
- }));
1895
- init.fkChanged = fkDiff.changed.map((fk) => ({
1896
- fields: fkColumns(fk.desired).fields,
1897
- targetTable: fk.desired.targetTable,
1898
- details: buildFkChangeDetails({
1899
- ...fk.desired,
1900
- targetFields: fkColumns(fk.desired).targetFields
1901
- }, fk.existing)
1902
- }));
2043
+ Object.assign(init, describeForeignKeyChanges(fkDiff));
1903
2044
  }
1904
2045
  if (facts.diff?.primaryKeyChanged) {
1905
2046
  const tableName = pendingRename ? dbName : void 0;
@@ -1917,6 +2058,26 @@ var SchemaSync = class {
1917
2058
  return facts;
1918
2059
  }
1919
2060
  /**
2061
+ * Folds the index / foreign-key work on each live table as it is now
2062
+ * (`planIndexes` / `planForeignKeys`) into its plan entry. Read only when
2063
+ * plan entries are reported (`plan()`, a refused run) — a run reports what
2064
+ * `syncIndexes` / `syncForeignKeys` did.
2065
+ */
2066
+ async planLiveWork(d, safe) {
2067
+ await Promise.all(d.tables.map((t) => this.planTableLiveWork(t, safe)));
2068
+ }
2069
+ async planTableLiveWork(facts, safe) {
2070
+ const adapter = facts.readable.dbAdapter;
2071
+ const init = facts.planEntry;
2072
+ if (!(init.status !== "create" && init.status !== "error" && (safe || !init.recreated) && !facts.pendingRename && !runRecreatesTable(facts, safe))) return;
2073
+ const indexChanges = await adapter.planIndexes(plannedIndexOptions(facts, safe));
2074
+ if (indexChanges.length > 0) {
2075
+ init.indexChanges = indexChanges;
2076
+ init.status = "alter";
2077
+ }
2078
+ if (adapter.planForeignKeys && mergeLiveForeignKeyChanges(init, await adapter.planForeignKeys())) init.status = "alter";
2079
+ }
2080
+ /**
1920
2081
  * Pure validation over the discovery facts. Every refusal is a change that
1921
2082
  * no order of DDL can apply safely; the messages are user-facing.
1922
2083
  */
@@ -2282,6 +2443,7 @@ var SchemaSync = class {
2282
2443
  await this.store.ensureControlTable();
2283
2444
  const d = await this.discover(resolved, safe);
2284
2445
  const refusals = this.preflight(d, safe);
2446
+ await this.planLiveWork(d, safe);
2285
2447
  const entries = this.buildPlanEntries(d, safe, refusals);
2286
2448
  if (!force) {
2287
2449
  if (await this.store.readHash() === hash) return {
@@ -2420,4 +2582,4 @@ async function planSchema(space, types, opts) {
2420
2582
  return new SchemaSync(space).plan(types, opts);
2421
2583
  }
2422
2584
  //#endregion
2423
- export { SchemaSync, SyncEntry, canonicalizeQueryNode, computeColumnDiff, computeForeignKeyDiff, computeSchemaHash, computeTableHash, computeTableOptionDiff, computeTableSnapshot, computeViewSnapshot, fkKey, hasForeignKeyChanges, planSchema, readStoredSnapshot, snapshotToExistingColumns, snapshotToExistingTableOptions, syncSchema, topoOrder };
2585
+ export { SchemaSync, SyncEntry, canonicalizeQueryNode, computeColumnDiff, computeForeignKeyDiff, computeSchemaHash, computeTableHash, computeTableOptionDiff, computeTableSnapshot, computeViewSnapshot, fkKey, hasForeignKeyChanges, planSchema, readStoredSnapshot, sameDefaultValue, snapshotToExistingColumns, snapshotToExistingTableOptions, syncSchema, topoOrder };