@atscript/db 0.1.154 → 0.1.156
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agg.d.cts +1 -1
- package/dist/agg.d.mts +1 -1
- package/dist/{buckets-CBKL_ZWE.d.mts → buckets-8mG67Mtw.d.mts} +168 -16
- package/dist/{buckets-jy8BfswO.d.cts → buckets-B4T_alUe.d.cts} +168 -16
- package/dist/{column-diff-DAIAq6XR.mjs → column-diff-DR1glykD.mjs} +419 -75
- package/dist/{column-diff-qnzx1o3c.cjs → column-diff-DpeUbhEI.cjs} +430 -74
- package/dist/{fk-diff-BIJ8l_hF.d.mts → fk-diff-5gX42hBt.d.mts} +2 -1
- package/dist/{fk-diff-D1mrLMjU.d.cts → fk-diff-BWdQ7kRZ.d.cts} +2 -1
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +15 -9
- package/dist/index.d.mts +15 -9
- package/dist/index.mjs +3 -3
- package/dist/plugin.cjs +21 -4
- package/dist/plugin.mjs +21 -4
- package/dist/rel.d.cts +2 -2
- package/dist/rel.d.mts +2 -2
- package/dist/{relation-helpers-BUsxNNGU.d.mts → relation-helpers-17_2f5hX.d.mts} +1 -1
- package/dist/{relation-helpers-D_8bydc2.d.cts → relation-helpers-BLBrLQYi.d.cts} +1 -1
- package/dist/{search-fields-DBIZKwyb.mjs → search-fields-CE8cmj9X.mjs} +1 -0
- package/dist/{search-fields-DwrsJUC1.cjs → search-fields-CT_UZ9fg.cjs} +1 -0
- package/dist/shared.cjs +2 -1
- package/dist/shared.d.cts +10 -3
- package/dist/shared.d.mts +10 -3
- package/dist/shared.mjs +2 -2
- package/dist/sync.cjs +317 -35
- package/dist/sync.d.cts +34 -5
- package/dist/sync.d.mts +34 -5
- package/dist/sync.mjs +317 -35
- package/dist/{validation-utils-DqZAH6aT.mjs → validation-utils-BGjqc2jU.mjs} +24 -6
- package/dist/{validation-utils-debIYpq0.cjs → validation-utils-CtLNlBtB.cjs} +29 -5
- package/dist/{validator-BACewoRj.mjs → validator-B3fPJjS1.mjs} +178 -10
- package/dist/{validator-BxWwh3wf.cjs → validator-B4YDLUkj.cjs} +201 -9
- package/dist/validator.cjs +1 -1
- package/dist/validator.mjs +1 -1
- package/package.json +6 -6
package/dist/sync.cjs
CHANGED
|
@@ -1,6 +1,174 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_db_error = require("./db-error-JcfJqqpk.cjs");
|
|
3
|
-
const require_column_diff = require("./column-diff-
|
|
3
|
+
const require_column_diff = require("./column-diff-DpeUbhEI.cjs");
|
|
4
|
+
const require_validator = require("./validator-B4YDLUkj.cjs");
|
|
5
|
+
//#region src/schema/json-column-copy.ts
|
|
6
|
+
const NONE = {
|
|
7
|
+
copies: [],
|
|
8
|
+
jsonify: [],
|
|
9
|
+
errors: []
|
|
10
|
+
};
|
|
11
|
+
/** A text column type of the old layout (`TEXT`, `LONGTEXT`, `VARCHAR(n)`, `character varying`). */
|
|
12
|
+
const TEXT_TYPE = /text|char|clob/i;
|
|
13
|
+
/**
|
|
14
|
+
* Design types a stored snapshot gives a field whose column held a whole
|
|
15
|
+
* value as JSON text: the old union layout (`union`), or `@db.json`.
|
|
16
|
+
*/
|
|
17
|
+
const JSON_TEXT_DESIGN_TYPES = new Set([
|
|
18
|
+
"union",
|
|
19
|
+
"json",
|
|
20
|
+
"object"
|
|
21
|
+
]);
|
|
22
|
+
/** How a target column reads its value out of the JSON, or why it cannot. */
|
|
23
|
+
function targetKind(fd) {
|
|
24
|
+
if (fd.encrypted) return { problem: "it is @db.encrypted" };
|
|
25
|
+
if (fd.isGeoPoint) return { problem: "it is a db.geoPoint" };
|
|
26
|
+
if (fd.storage === "json") return "json";
|
|
27
|
+
return fd.designType === "boolean" ? "boolean" : "text";
|
|
28
|
+
}
|
|
29
|
+
/** Whether `type` is a union one of whose value members is a string. */
|
|
30
|
+
function hasStringMember(type) {
|
|
31
|
+
if (type?.type.kind !== "union") return false;
|
|
32
|
+
return require_validator.unionValueMembers(type).members.some((m) => require_column_diff.resolveDesignType(m) === "string");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Plans {@link TJsonColumnMigration} for one table from its live column diff
|
|
36
|
+
* (and its stored snapshot — on SQLite the old text column and a JSON
|
|
37
|
+
* column have the same type).
|
|
38
|
+
*/
|
|
39
|
+
function planJsonColumnMigration(readable, diff, snapshot) {
|
|
40
|
+
const meta = readable.getMetadata();
|
|
41
|
+
if (meta.nestedObjects) return NONE;
|
|
42
|
+
const adapter = readable.dbAdapter;
|
|
43
|
+
const name = readable.tableName;
|
|
44
|
+
const removed = new Set(diff.removed.map((c) => c.name));
|
|
45
|
+
const snapshotFields = new Map(snapshot?.fields.map((f) => [f.physicalName, f]));
|
|
46
|
+
const out = {
|
|
47
|
+
copies: [],
|
|
48
|
+
jsonify: [],
|
|
49
|
+
errors: []
|
|
50
|
+
};
|
|
51
|
+
if (removed.size > 0) for (const parent of meta.flattenedParents) {
|
|
52
|
+
const source = require_column_diff.relationalColumnName(parent, void 0, parent.includes("."));
|
|
53
|
+
if (!removed.has(source)) continue;
|
|
54
|
+
const was = snapshotFields.get(source);
|
|
55
|
+
if (was && !JSON_TEXT_DESIGN_TYPES.has(was.designType)) continue;
|
|
56
|
+
const prefix = `${parent}.`;
|
|
57
|
+
const targets = [];
|
|
58
|
+
const problems = [];
|
|
59
|
+
for (const fd of meta.storedDescriptors) {
|
|
60
|
+
if (!fd.path.startsWith(prefix)) continue;
|
|
61
|
+
const kind = targetKind(fd);
|
|
62
|
+
if (typeof kind === "object") {
|
|
63
|
+
problems.push(`"${fd.physicalName}" (${kind.problem})`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
targets.push({
|
|
67
|
+
column: fd.physicalName,
|
|
68
|
+
path: fd.path.slice(prefix.length).split("."),
|
|
69
|
+
kind,
|
|
70
|
+
field: fd
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (targets.length === 0 && problems.length === 0) continue;
|
|
74
|
+
if (problems.length > 0 || !adapter.copyFromJsonColumn) {
|
|
75
|
+
const why = problems.length > 0 ? `cannot fill ${problems.join(", ")} from it` : "the adapter cannot copy JSON values into columns";
|
|
76
|
+
out.errors.push(`Column "${source}" of ${name} holds "${parent}" as JSON text (the layout of atscript-db ≤ 0.1.154) and ${why}. Annotate "${parent}" with @db.json to keep it in one column, or migrate the data and drop "${source}" manually.`);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
out.copies.push({
|
|
80
|
+
source,
|
|
81
|
+
targets
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const typeChanged = new Map(diff.typeChanged.map((tc) => [tc.field.physicalName, tc]));
|
|
85
|
+
for (const fd of meta.storedDescriptors) {
|
|
86
|
+
if (fd.storage !== "json" || !hasStringMember(meta.flatMap.get(fd.path))) continue;
|
|
87
|
+
const tc = typeChanged.get(fd.physicalName);
|
|
88
|
+
const old = snapshotFields.get(fd.physicalName);
|
|
89
|
+
const dotted = `${fd.physicalName}.`;
|
|
90
|
+
if (!(tc !== void 0 && TEXT_TYPE.test(tc.existingType) || old !== void 0 && old.storage !== "json" && old.designType !== "json" || diff.removed.some((c) => c.name.startsWith(dotted)))) continue;
|
|
91
|
+
if (!adapter.jsonifyTextColumn) {
|
|
92
|
+
out.errors.push(`Column "${fd.physicalName}" of ${name} becomes a JSON column but the adapter cannot convert its text values. Migrate them manually.`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
out.jsonify.push(fd.physicalName);
|
|
96
|
+
}
|
|
97
|
+
return out.copies.length > 0 || out.jsonify.length > 0 || out.errors.length > 0 ? out : NONE;
|
|
98
|
+
}
|
|
99
|
+
/** The copy targets {@link applyJsonColumnMigration} adds: those not in the table yet. */
|
|
100
|
+
function copyTargetsToAdd(migration, diff) {
|
|
101
|
+
const targets = new Set(migration.copies.flatMap((c) => c.targets.map((t) => t.column)));
|
|
102
|
+
return diff.added.filter((fd) => targets.has(fd.physicalName));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The column diff the rest of the table's sync applies after
|
|
106
|
+
* {@link applyJsonColumnMigration}: without the copy targets it added, with
|
|
107
|
+
* their NOT NULL and model default as nullability / default changes. Also
|
|
108
|
+
* what `plan({ safe: true })` reports as skipped — safe mode adds and fills
|
|
109
|
+
* the targets but leaves them nullable and without their default.
|
|
110
|
+
*/
|
|
111
|
+
function diffAfterJsonCopy(migration, diff) {
|
|
112
|
+
const toAdd = copyTargetsToAdd(migration, diff);
|
|
113
|
+
if (toAdd.length === 0) return diff;
|
|
114
|
+
return {
|
|
115
|
+
...diff,
|
|
116
|
+
added: diff.added.filter((fd) => !toAdd.includes(fd)),
|
|
117
|
+
nullableChanged: [...diff.nullableChanged, ...toAdd.filter((fd) => !fd.optional).map((fd) => ({
|
|
118
|
+
field: fd,
|
|
119
|
+
wasNullable: true
|
|
120
|
+
}))],
|
|
121
|
+
defaultChanged: [...diff.defaultChanged, ...toAdd.filter((fd) => fd.defaultValue !== void 0).map((fd) => ({
|
|
122
|
+
field: fd,
|
|
123
|
+
newDefault: require_column_diff.serializeDefaultValue(fd.defaultValue)
|
|
124
|
+
}))]
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Runs a planned {@link TJsonColumnMigration} — first, before any other
|
|
129
|
+
* column work of the table: text columns become JSON text, the copy targets
|
|
130
|
+
* that do not exist yet are added, and the values are copied. Returns the
|
|
131
|
+
* diff without the columns it added ({@link diffAfterJsonCopy}). Idempotent:
|
|
132
|
+
* a re-run converts nothing twice and copies only into rows whose targets
|
|
133
|
+
* are all NULL.
|
|
134
|
+
*
|
|
135
|
+
* The targets are added nullable and WITHOUT a default — `ADD COLUMN …
|
|
136
|
+
* DEFAULT`, or the type default an adapter gives a required column (a
|
|
137
|
+
* leaf every member of a non-null union of objects declares), fills every
|
|
138
|
+
* existing row, so the copy would skip them all. A model default and NOT
|
|
139
|
+
* NULL are returned as default / nullability changes of the diff, which the
|
|
140
|
+
* rest of the table's sync applies after the copy (`SET DEFAULT` / `SET NOT
|
|
141
|
+
* NULL` / `MODIFY COLUMN`, a table recreation on SQLite). Rows the copy
|
|
142
|
+
* leaves NULL keep NULL in a nullable column; a required one backfills them.
|
|
143
|
+
*/
|
|
144
|
+
async function applyJsonColumnMigration(adapter, migration, diff) {
|
|
145
|
+
for (const column of migration.jsonify) await adapter.jsonifyTextColumn(column);
|
|
146
|
+
if (migration.copies.length === 0) return {
|
|
147
|
+
diff,
|
|
148
|
+
added: []
|
|
149
|
+
};
|
|
150
|
+
const toAdd = copyTargetsToAdd(migration, diff);
|
|
151
|
+
let added = [];
|
|
152
|
+
if (toAdd.length > 0) added = (await adapter.syncColumns({
|
|
153
|
+
added: toAdd.map((fd) => ({
|
|
154
|
+
...fd,
|
|
155
|
+
optional: true,
|
|
156
|
+
defaultValue: void 0
|
|
157
|
+
})),
|
|
158
|
+
removed: [],
|
|
159
|
+
renamed: [],
|
|
160
|
+
typeChanged: [],
|
|
161
|
+
nullableChanged: [],
|
|
162
|
+
defaultChanged: [],
|
|
163
|
+
conflicts: []
|
|
164
|
+
})).added;
|
|
165
|
+
for (const copy of migration.copies) await adapter.copyFromJsonColumn(copy.source, copy.targets);
|
|
166
|
+
return {
|
|
167
|
+
diff: diffAfterJsonCopy(migration, diff),
|
|
168
|
+
added
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
4
172
|
//#region src/schema/table-option-diff.ts
|
|
5
173
|
/**
|
|
6
174
|
* Computes the difference between desired and existing table options.
|
|
@@ -370,6 +538,10 @@ var SyncEntry = class SyncEntry {
|
|
|
370
538
|
fkChanged;
|
|
371
539
|
/** @since 0.1.141 — see {@link TSyncEntryInit.derivedChanges}. */
|
|
372
540
|
derivedChanges;
|
|
541
|
+
/** @since 0.1.155 — see {@link TSyncEntryInit.jsonCopies}. */
|
|
542
|
+
jsonCopies;
|
|
543
|
+
/** @since 0.1.155 — see {@link TSyncEntryInit.jsonified}. */
|
|
544
|
+
jsonified;
|
|
373
545
|
columnsAdded;
|
|
374
546
|
columnsRenamed;
|
|
375
547
|
columnsDropped;
|
|
@@ -404,6 +576,8 @@ var SyncEntry = class SyncEntry {
|
|
|
404
576
|
this.fkRemoved = init.fkRemoved ?? [];
|
|
405
577
|
this.fkChanged = init.fkChanged ?? [];
|
|
406
578
|
this.derivedChanges = init.derivedChanges ?? [];
|
|
579
|
+
this.jsonCopies = init.jsonCopies ?? [];
|
|
580
|
+
this.jsonified = init.jsonified ?? [];
|
|
407
581
|
this.columnsAdded = init.columnsAdded ?? [];
|
|
408
582
|
this.columnsRenamed = init.columnsRenamed ?? [];
|
|
409
583
|
this.columnsDropped = init.columnsDropped ?? [];
|
|
@@ -477,13 +651,13 @@ var SyncEntry = class SyncEntry {
|
|
|
477
651
|
const head = this.refused ? `✖ refused: ${vp}${label}` : `✗ ${vp}${label} — error`;
|
|
478
652
|
return [` ${c.red(head)}`, ...this.errors.map((err) => ` ${c.red(err)}`)];
|
|
479
653
|
}
|
|
480
|
-
/** `! PK (id) → (code) — rebuild (table is empty)` / `— skipped (safe mode)` */
|
|
654
|
+
/** `! PK (id) → (code) — rebuild (table is empty)` / `— rebuild (rows satisfy the new key)` / `— skipped (safe mode)` */
|
|
481
655
|
printPkChange(c, mode) {
|
|
482
656
|
const pk = this.pkChange;
|
|
483
657
|
if (!pk) return [];
|
|
484
658
|
const cols = `PK (${pk.from.join(", ")}) → (${pk.to.join(", ")})`;
|
|
485
659
|
if (!pk.rebuild) return [` ${c.yellow(`! ${cols} — skipped (safe mode)`)}`];
|
|
486
|
-
return mode === "plan" ? [` ${c.red(`! ${cols} — rebuild (table is empty)`)}`] : [` ${c.yellow(`~ ${cols} — rebuilt`)}`];
|
|
660
|
+
return mode === "plan" ? [` ${c.red(`! ${cols} — rebuild (${pk.populated ? "rows satisfy the new key" : "table is empty"})`)}`] : [` ${c.yellow(`~ ${cols} — rebuilt`)}`];
|
|
487
661
|
}
|
|
488
662
|
/**
|
|
489
663
|
* `! col: t1 → t2 — drop` (plan), or `! type col (t1 → t2) — skipped (safe
|
|
@@ -509,6 +683,11 @@ var SyncEntry = class SyncEntry {
|
|
|
509
683
|
return ` ${dc.reason === "kind" ? c.red(line) : c.yellow(line)}`;
|
|
510
684
|
});
|
|
511
685
|
}
|
|
686
|
+
/** `~ addr → addr__street, addr__zip — copy JSON values` / `~ extra — text values as JSON`. */
|
|
687
|
+
printJsonMigration(c, mode) {
|
|
688
|
+
const copy = mode === "plan" ? "copy JSON values" : "JSON values copied";
|
|
689
|
+
return [...this.jsonified.map((col) => ` ${c.yellow(`~ ${col} — text values as JSON`)}`), ...this.jsonCopies.map((jc) => ` ${c.yellow(`~ ${jc.from} → ${jc.to.join(", ")} — ${copy}`)}`)];
|
|
690
|
+
}
|
|
512
691
|
/** `~ col — nullable` / `~ col — default a → b`, `— skipped (safe mode)` when pending. */
|
|
513
692
|
printNullableDefaults(c) {
|
|
514
693
|
const suffix = this.skipped.includes("nullable-defaults") ? " — skipped (safe mode)" : "";
|
|
@@ -566,6 +745,7 @@ var SyncEntry = class SyncEntry {
|
|
|
566
745
|
` ${c.cyan(`~ ${vp}${label} — alter${renameInfo}`)}`,
|
|
567
746
|
...this.columnsToAdd.map((col) => ` ${c.green(`+ ${col.physicalName} (${col.designType})${col.derived ? " derived" : ""} — add`)}`),
|
|
568
747
|
...this.columnsToRename.map((r) => ` ${c.yellow(`~ ${r.from} → ${r.to} — rename`)}`),
|
|
748
|
+
...this.printJsonMigration(c, "plan"),
|
|
569
749
|
...this.printTypeChanges(c),
|
|
570
750
|
...this.printNullableDefaults(c),
|
|
571
751
|
...this.printDerivedChanges(c, "plan"),
|
|
@@ -594,13 +774,14 @@ var SyncEntry = class SyncEntry {
|
|
|
594
774
|
...this.columnsAdded.map((col) => ` ${c.green(`+ ${col} — added`)}`),
|
|
595
775
|
""
|
|
596
776
|
];
|
|
597
|
-
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.skipped.length > 0 || this.recreated || this.renamedFrom) {
|
|
777
|
+
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) {
|
|
598
778
|
const rlabel = this.recreated ? "recreated" : "altered";
|
|
599
779
|
const renameInfo = this.renamedFrom ? ` ${c.yellow(`(renamed from ${this.renamedFrom})`)}` : "";
|
|
600
780
|
return [
|
|
601
781
|
` ${(this.recreated ? (s) => c.yellow(s) : (s) => c.cyan(s))(`~ ${vp}${label} — ${rlabel}${renameInfo}`)}`,
|
|
602
782
|
...this.columnsAdded.map((col) => ` ${c.green(`+ ${col} — added`)}`),
|
|
603
783
|
...this.columnsRenamed.map((col) => ` ${c.yellow(`~ ${col} — renamed`)}`),
|
|
784
|
+
...this.printJsonMigration(c, "result"),
|
|
604
785
|
...this.printPkChange(c, "result"),
|
|
605
786
|
...this.printTypeChanges(c),
|
|
606
787
|
...this.printNullableDefaults(c),
|
|
@@ -642,14 +823,19 @@ function planTableInit(facts, safe, dependsOn) {
|
|
|
642
823
|
};
|
|
643
824
|
const skipped = safe ? safeModeSkips(facts) : [];
|
|
644
825
|
const pk = facts.diff?.primaryKeyChanged;
|
|
645
|
-
if (pk)
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
826
|
+
if (pk) {
|
|
827
|
+
init.pkChange = {
|
|
828
|
+
from: pk.from,
|
|
829
|
+
to: pk.to,
|
|
830
|
+
rebuild: !skipped.includes("pk-rebuild")
|
|
831
|
+
};
|
|
832
|
+
if (facts.populated === true) init.pkChange.populated = true;
|
|
833
|
+
}
|
|
650
834
|
if (!safe) return init;
|
|
835
|
+
const diff = afterJsonCopy(facts);
|
|
651
836
|
return {
|
|
652
837
|
...init,
|
|
838
|
+
...diff && describeNullableDefaults(diff),
|
|
653
839
|
columnsToDrop: [],
|
|
654
840
|
typeChanges: skipped.includes("recreate") ? init.typeChanges : [],
|
|
655
841
|
derivedChanges: skipped.includes("derived") ? init.derivedChanges : [],
|
|
@@ -667,6 +853,7 @@ function typeChangeStrategy(readable, diff) {
|
|
|
667
853
|
const adapter = readable.dbAdapter;
|
|
668
854
|
if (willDropRecreate(readable, diff, void 0)) return "drop";
|
|
669
855
|
if (readable.syncMethod === "recreate" && adapter.recreateTable) return "recreate";
|
|
856
|
+
if (diff.primaryKeyChanged && !adapter.rebuildPrimaryKey && adapter.recreateTable) return "recreate";
|
|
670
857
|
if (adapter.supportsColumnModify && adapter.syncColumns) return "modify";
|
|
671
858
|
return "none";
|
|
672
859
|
}
|
|
@@ -734,11 +921,23 @@ async function executeSyncTable(facts, safe, deps, exec) {
|
|
|
734
921
|
await adapter.ensureTable(ensureOpts);
|
|
735
922
|
init.status = "create";
|
|
736
923
|
} else if (existing.length > 0) {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
924
|
+
let diff = facts.diff ?? require_column_diff.computeColumnDiff(readable.columnDescriptors, existing, adapter.typeMapper?.bind(adapter), {
|
|
925
|
+
snapshot: storedSnapshot,
|
|
926
|
+
nativeDefaultFns: adapter.nativeDefaultFns()
|
|
927
|
+
});
|
|
928
|
+
const migration = facts.jsonMigration ?? planJsonColumnMigration(readable, diff, storedSnapshot);
|
|
929
|
+
if (migration.errors.length > 0) throw new Error(migration.errors.join(" "));
|
|
930
|
+
if (migration.copies.length > 0 || migration.jsonify.length > 0) {
|
|
931
|
+
phase = "JSON column copy";
|
|
932
|
+
const applied = await applyJsonColumnMigration(adapter, migration, diff);
|
|
933
|
+
diff = applied.diff;
|
|
934
|
+
Object.assign(init, describeJsonMigration(migration));
|
|
935
|
+
if (applied.added.length > 0) init.columnsAdded = applied.added;
|
|
741
936
|
init.status = "alter";
|
|
937
|
+
phase = "Column sync";
|
|
938
|
+
}
|
|
939
|
+
if (hasFkChanges && !adapter.syncForeignKeys && adapter.recreateTable) {
|
|
940
|
+
await recreateKeepingRenames(adapter, diff, init);
|
|
742
941
|
if (diff.primaryKeyChanged) init.pkChange = {
|
|
743
942
|
...diff.primaryKeyChanged,
|
|
744
943
|
rebuild: true
|
|
@@ -747,8 +946,11 @@ async function executeSyncTable(facts, safe, deps, exec) {
|
|
|
747
946
|
if (hasFkChanges && fkDiff && adapter.dropForeignKeys) {
|
|
748
947
|
const keysToDrop = [...fkDiff.removed.map((fk) => require_column_diff.fkKey(fk.fields)), ...fkDiff.changed.map((fk) => require_column_diff.fkKey(require_column_diff.fkColumns(fk.desired).fields))];
|
|
749
948
|
if (keysToDrop.length > 0) {
|
|
750
|
-
|
|
751
|
-
|
|
949
|
+
const fresh = keysToDrop.filter((key) => !droppedFks.includes(key));
|
|
950
|
+
if (fresh.length > 0) {
|
|
951
|
+
await adapter.dropForeignKeys(fresh);
|
|
952
|
+
droppedFks.push(...fresh);
|
|
953
|
+
}
|
|
752
954
|
init.status = "alter";
|
|
753
955
|
}
|
|
754
956
|
}
|
|
@@ -759,7 +961,10 @@ async function executeSyncTable(facts, safe, deps, exec) {
|
|
|
759
961
|
const existed = adapter.tableExists ? await adapter.tableExists() : false;
|
|
760
962
|
await adapter.ensureTable(ensureOpts);
|
|
761
963
|
if (!existed) init.status = "create";
|
|
762
|
-
} else await applyColumnDiff(adapter, readable, facts.diff ?? require_column_diff.computeColumnDiff(readable.columnDescriptors, require_column_diff.snapshotToExistingColumns(storedSnapshot, readable), deps.resolveTypeMapper(adapter), {
|
|
964
|
+
} else await applyColumnDiff(adapter, readable, facts.diff ?? require_column_diff.computeColumnDiff(readable.columnDescriptors, require_column_diff.snapshotToExistingColumns(storedSnapshot, readable), deps.resolveTypeMapper(adapter), {
|
|
965
|
+
snapshot: storedSnapshot,
|
|
966
|
+
nativeDefaultFns: adapter.nativeDefaultFns()
|
|
967
|
+
}), init, safe, deps.logger, ensureOpts);
|
|
763
968
|
else {
|
|
764
969
|
const existed = adapter.tableExists ? await adapter.tableExists() : true;
|
|
765
970
|
if (!init.recreated) {
|
|
@@ -798,9 +1003,10 @@ async function executeSyncTable(facts, safe, deps, exec) {
|
|
|
798
1003
|
}
|
|
799
1004
|
}
|
|
800
1005
|
}
|
|
1006
|
+
if (init.pkChange && facts.populated === true) init.pkChange.populated = true;
|
|
801
1007
|
if (init.status === "error") return new SyncEntry(init);
|
|
802
1008
|
phase = "Index/FK sync";
|
|
803
|
-
await adapter.syncIndexes();
|
|
1009
|
+
await adapter.syncIndexes(keptIndexes(init));
|
|
804
1010
|
if (adapter.syncForeignKeys && !exec.deferForeignKeysTo) await adapter.syncForeignKeys();
|
|
805
1011
|
if (adapter.afterSyncTable) await adapter.afterSyncTable();
|
|
806
1012
|
} catch (error) {
|
|
@@ -901,13 +1107,40 @@ function willDropRecreate(readable, diff, optionDiff) {
|
|
|
901
1107
|
if (readable.syncMethod === "drop" && (diff?.typeChanged.length ?? 0) > 0) return true;
|
|
902
1108
|
return (optionDiff?.changed.some((c) => c.destructive) ?? false) && !(readable.syncMethod === "recreate" && !!adapter.recreateTable);
|
|
903
1109
|
}
|
|
1110
|
+
/**
|
|
1111
|
+
* The column diff the executor applies after the table's JSON column copy —
|
|
1112
|
+
* its targets' NOT NULL and defaults deferred (see {@link diffAfterJsonCopy}).
|
|
1113
|
+
*/
|
|
1114
|
+
function afterJsonCopy(facts) {
|
|
1115
|
+
return facts.diff && facts.jsonMigration ? diffAfterJsonCopy(facts.jsonMigration, facts.diff) : facts.diff;
|
|
1116
|
+
}
|
|
904
1117
|
/** Records work safe mode skipped on the entry (see `SyncEntry.skipped`). */
|
|
905
1118
|
function markSkipped(init, kind) {
|
|
906
1119
|
init.skipped = [...init.skipped ?? [], kind];
|
|
907
1120
|
}
|
|
908
|
-
/**
|
|
1121
|
+
/**
|
|
1122
|
+
* Whether nullable/default changes need DDL on this adapter: in-place modify,
|
|
1123
|
+
* or a recreate on a live-introspected one (SQLite). Snapshot-based adapters
|
|
1124
|
+
* (MongoDB) have no column constraint to change — they only update the
|
|
1125
|
+
* snapshot, even with a `recreateTable`.
|
|
1126
|
+
*/
|
|
909
1127
|
function needsDdlForNullableDefaults(adapter) {
|
|
910
|
-
return !!adapter.supportsColumnModify && !!adapter.syncColumns || !!adapter.recreateTable;
|
|
1128
|
+
return !!adapter.supportsColumnModify && !!adapter.syncColumns || !!adapter.recreateTable && !!adapter.getExistingColumns;
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* The indexes `syncIndexes` keeps as they are because safe mode skipped
|
|
1132
|
+
* the change they depend on — a skipped key rebuild, the columns of a
|
|
1133
|
+
* skipped type-change recreate (see `TSyncIndexesOptions`).
|
|
1134
|
+
*/
|
|
1135
|
+
function keptIndexes(init) {
|
|
1136
|
+
const skipped = init.skipped ?? [];
|
|
1137
|
+
const keepPrimaryKey = skipped.includes("pk-rebuild");
|
|
1138
|
+
const keepColumns = skipped.includes("recreate") ? (init.typeChanges ?? []).map((tc) => tc.column) : [];
|
|
1139
|
+
if (!keepPrimaryKey && keepColumns.length === 0) return;
|
|
1140
|
+
return {
|
|
1141
|
+
keepPrimaryKey,
|
|
1142
|
+
keepColumns
|
|
1143
|
+
};
|
|
911
1144
|
}
|
|
912
1145
|
/**
|
|
913
1146
|
* The work `run({ safe: true })` skips on this table, in the order the
|
|
@@ -918,7 +1151,8 @@ function needsDdlForNullableDefaults(adapter) {
|
|
|
918
1151
|
* DDL either way.
|
|
919
1152
|
*/
|
|
920
1153
|
function safeModeSkips(facts) {
|
|
921
|
-
const { readable,
|
|
1154
|
+
const { readable, optionDiff, fkDiff } = facts;
|
|
1155
|
+
const diff = afterJsonCopy(facts);
|
|
922
1156
|
const adapter = readable.dbAdapter;
|
|
923
1157
|
const out = [];
|
|
924
1158
|
if (facts.planEntry.status === "error") return out;
|
|
@@ -932,6 +1166,16 @@ function safeModeSkips(facts) {
|
|
|
932
1166
|
if (optionDiff?.changed.some((c) => c.destructive)) out.push("table-options");
|
|
933
1167
|
return out;
|
|
934
1168
|
}
|
|
1169
|
+
/** The plan-shaped summary of a {@link TJsonColumnMigration}. */
|
|
1170
|
+
function describeJsonMigration(migration) {
|
|
1171
|
+
return {
|
|
1172
|
+
jsonCopies: migration.copies.length > 0 ? migration.copies.map((c) => ({
|
|
1173
|
+
from: c.source,
|
|
1174
|
+
to: c.targets.map((t) => t.column)
|
|
1175
|
+
})) : void 0,
|
|
1176
|
+
jsonified: migration.jsonify.length > 0 ? migration.jsonify : void 0
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
935
1179
|
/** The plan-shaped nullable / default change summaries of a diff. */
|
|
936
1180
|
function describeNullableDefaults(diff) {
|
|
937
1181
|
return {
|
|
@@ -963,6 +1207,28 @@ async function dropAndRecreate(adapter, init, name, ensureOpts) {
|
|
|
963
1207
|
init.recreated = true;
|
|
964
1208
|
init.status = "alter";
|
|
965
1209
|
}
|
|
1210
|
+
/**
|
|
1211
|
+
* `recreateTable()` before the column diff is applied: it copies the rows by
|
|
1212
|
+
* column name, so the columns renamed in this run are renamed first — under
|
|
1213
|
+
* the old name their values would not be copied.
|
|
1214
|
+
*/
|
|
1215
|
+
async function recreateKeepingRenames(adapter, diff, init) {
|
|
1216
|
+
if (diff.renamed.length > 0 && adapter.syncColumns) {
|
|
1217
|
+
const { renamed } = await adapter.syncColumns({
|
|
1218
|
+
added: [],
|
|
1219
|
+
removed: [],
|
|
1220
|
+
renamed: diff.renamed,
|
|
1221
|
+
typeChanged: [],
|
|
1222
|
+
nullableChanged: [],
|
|
1223
|
+
defaultChanged: [],
|
|
1224
|
+
conflicts: []
|
|
1225
|
+
});
|
|
1226
|
+
init.columnsRenamed = renamed;
|
|
1227
|
+
}
|
|
1228
|
+
await adapter.recreateTable();
|
|
1229
|
+
init.recreated = true;
|
|
1230
|
+
init.status = "alter";
|
|
1231
|
+
}
|
|
966
1232
|
async function applyColumnDiff(adapter, readable, diff, init, safe, logger, ensureOpts) {
|
|
967
1233
|
const name = readable.tableName;
|
|
968
1234
|
if (diff.conflicts.length > 0) {
|
|
@@ -981,11 +1247,8 @@ async function applyColumnDiff(adapter, readable, diff, init, safe, logger, ensu
|
|
|
981
1247
|
const cols = init.typeChanges.map((tc) => `${tc.column}: ${tc.fromType} → ${tc.toType}`).join(", ");
|
|
982
1248
|
logger.warn?.(`[schema-sync] Type change on "${name}" (${cols}) — drop-and-recreate skipped (safe mode)`);
|
|
983
1249
|
} else await dropAndRecreate(adapter, init, name, ensureOpts);
|
|
984
|
-
else if (strategy === "recreate")
|
|
985
|
-
|
|
986
|
-
init.recreated = true;
|
|
987
|
-
init.status = "alter";
|
|
988
|
-
} else if (strategy === "modify") {
|
|
1250
|
+
else if (strategy === "recreate") await recreateKeepingRenames(adapter, diff, init);
|
|
1251
|
+
else if (strategy === "modify") {
|
|
989
1252
|
needsSyncColumns = true;
|
|
990
1253
|
init.status = "alter";
|
|
991
1254
|
} else {
|
|
@@ -999,11 +1262,8 @@ async function applyColumnDiff(adapter, readable, diff, init, safe, logger, ensu
|
|
|
999
1262
|
if (!safe && !init.recreated && init.status !== "error") if (adapter.supportsColumnModify && adapter.syncColumns) {
|
|
1000
1263
|
needsSyncColumns = true;
|
|
1001
1264
|
init.status = "alter";
|
|
1002
|
-
} else if (adapter
|
|
1003
|
-
|
|
1004
|
-
init.recreated = true;
|
|
1005
|
-
init.status = "alter";
|
|
1006
|
-
} else init.status = "alter";
|
|
1265
|
+
} else if (needsDdlForNullableDefaults(adapter)) await recreateKeepingRenames(adapter, diff, init);
|
|
1266
|
+
else init.status = "alter";
|
|
1007
1267
|
else if (init.status !== "error") {
|
|
1008
1268
|
if (safe && !init.recreated && needsDdlForNullableDefaults(adapter)) {
|
|
1009
1269
|
Object.assign(init, describeNullableDefaults(diff));
|
|
@@ -1015,7 +1275,7 @@ async function applyColumnDiff(adapter, readable, diff, init, safe, logger, ensu
|
|
|
1015
1275
|
}
|
|
1016
1276
|
if (!init.recreated && init.status !== "error" && (diff.added.length > 0 || diff.renamed.length > 0 || needsSyncColumns) && adapter.syncColumns) {
|
|
1017
1277
|
const syncResult = await adapter.syncColumns(diff);
|
|
1018
|
-
init.columnsAdded = syncResult.added;
|
|
1278
|
+
init.columnsAdded = [...init.columnsAdded ?? [], ...syncResult.added];
|
|
1019
1279
|
init.columnsRenamed = syncResult.renamed;
|
|
1020
1280
|
if (syncResult.added.length > 0 || (syncResult.renamed?.length ?? 0) > 0 || needsSyncColumns) init.status = "alter";
|
|
1021
1281
|
}
|
|
@@ -1580,8 +1840,18 @@ var SchemaSync = class {
|
|
|
1580
1840
|
init.columnsToAdd = [...readable.columnDescriptors];
|
|
1581
1841
|
} else if (facts.existing && facts.existing.length > 0) {
|
|
1582
1842
|
const typeMapper = adapter.typeMapper?.bind(adapter);
|
|
1583
|
-
facts.diff = require_column_diff.computeColumnDiff(readable.columnDescriptors, facts.existing, typeMapper, {
|
|
1843
|
+
facts.diff = require_column_diff.computeColumnDiff(readable.columnDescriptors, facts.existing, typeMapper, {
|
|
1844
|
+
snapshot: storedSnapshot,
|
|
1845
|
+
nativeDefaultFns: adapter.nativeDefaultFns()
|
|
1846
|
+
});
|
|
1584
1847
|
this.populatePlanFromDiff(facts.diff, init, readable, safe);
|
|
1848
|
+
const migration = planJsonColumnMigration(readable, facts.diff, storedSnapshot);
|
|
1849
|
+
facts.jsonMigration = migration;
|
|
1850
|
+
Object.assign(init, describeJsonMigration(migration));
|
|
1851
|
+
if (migration.errors.length > 0) {
|
|
1852
|
+
init.status = "error";
|
|
1853
|
+
init.errors = [...init.errors ?? [], ...migration.errors];
|
|
1854
|
+
} else if (init.status === "in-sync" && (migration.copies.length > 0 || migration.jsonify.length > 0)) init.status = "alter";
|
|
1585
1855
|
}
|
|
1586
1856
|
} else if (adapter.syncColumns) if (!storedSnapshot) {
|
|
1587
1857
|
if (!pendingRename) {
|
|
@@ -1592,7 +1862,10 @@ var SchemaSync = class {
|
|
|
1592
1862
|
}
|
|
1593
1863
|
} else {
|
|
1594
1864
|
const existing = require_column_diff.snapshotToExistingColumns(storedSnapshot, readable);
|
|
1595
|
-
facts.diff = require_column_diff.computeColumnDiff(readable.columnDescriptors, existing, this.resolveTypeMapper(adapter), {
|
|
1865
|
+
facts.diff = require_column_diff.computeColumnDiff(readable.columnDescriptors, existing, this.resolveTypeMapper(adapter), {
|
|
1866
|
+
snapshot: storedSnapshot,
|
|
1867
|
+
nativeDefaultFns: adapter.nativeDefaultFns()
|
|
1868
|
+
});
|
|
1596
1869
|
this.populatePlanFromDiff(facts.diff, init, readable, safe);
|
|
1597
1870
|
}
|
|
1598
1871
|
else if (adapter.tableExists) {
|
|
@@ -1627,7 +1900,16 @@ var SchemaSync = class {
|
|
|
1627
1900
|
}, fk.existing)
|
|
1628
1901
|
}));
|
|
1629
1902
|
}
|
|
1630
|
-
if (facts.diff?.primaryKeyChanged)
|
|
1903
|
+
if (facts.diff?.primaryKeyChanged) {
|
|
1904
|
+
const tableName = pendingRename ? dbName : void 0;
|
|
1905
|
+
facts.populated = await adapter.hasRows(tableName) ?? "unknown";
|
|
1906
|
+
const { added, renamed } = facts.diff;
|
|
1907
|
+
const { to } = facts.diff.primaryKeyChanged;
|
|
1908
|
+
if (facts.populated === true && adapter.countKeyViolations && !added.some((f) => to.includes(f.physicalName))) {
|
|
1909
|
+
const liveName = new Map(renamed.map((r) => [r.field.physicalName, r.oldName]));
|
|
1910
|
+
facts.keyViolations = await adapter.countKeyViolations(to.map((c) => liveName.get(c) ?? c), tableName);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1631
1913
|
facts.dropsTable = !!facts.diff?.primaryKeyChanged || willDropRecreate(readable, facts.diff, facts.optionDiff);
|
|
1632
1914
|
if (facts.diff?.primaryKeyChanged || probeRecreateInbound && facts.dropsTable) facts.inboundFks = await adapter.getReferencingForeignKeys?.(dbName);
|
|
1633
1915
|
if (adapter.getObjectKind) facts.objectKind = await adapter.getObjectKind(dbName);
|
|
@@ -1644,7 +1926,7 @@ var SchemaSync = class {
|
|
|
1644
1926
|
const pk = t.diff?.primaryKeyChanged;
|
|
1645
1927
|
if (pk) {
|
|
1646
1928
|
const label = pkLabel(pk);
|
|
1647
|
-
if (t.populated === true) addRefusal(refusals, t.name, `Primary key of "${t.name}" changed ${label} but the table has rows; schema sync cannot rebuild a populated primary key. Migrate manually (or empty the table) and re-run.`);
|
|
1929
|
+
if (t.populated === true && t.keyViolations !== 0) addRefusal(refusals, t.name, t.keyViolations === void 0 ? `Primary key of "${t.name}" changed ${label} but the table has rows; schema sync cannot rebuild a populated primary key. Migrate manually (or empty the table) and re-run.` : `Primary key of "${t.name}" changed ${label} but ${t.keyViolations} row${t.keyViolations === 1 ? " has" : "s have"} a NULL or duplicate (${pk.to.join(", ")}) — fix or remove ${t.keyViolations === 1 ? "it" : "them"} (or migrate manually) and re-run.`);
|
|
1648
1930
|
else if (t.populated === "unknown") addRefusal(refusals, t.name, `Primary key of "${t.name}" changed ${label} but the adapter cannot tell whether the table has rows under its old name "${t.dbName}" — implement hasRows(tableName) on the adapter, or rename the table in a separate run first.`);
|
|
1649
1931
|
if (!safe) {
|
|
1650
1932
|
const liveInbound = (t.inboundFks ?? []).filter((fk) => {
|
package/dist/sync.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
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-B4T_alUe.cjs";
|
|
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-BWdQ7kRZ.cjs";
|
|
3
3
|
import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
|
|
4
4
|
|
|
5
5
|
//#region src/schema/sync-entry.d.ts
|
|
@@ -77,6 +77,22 @@ interface TSyncEntryInit {
|
|
|
77
77
|
* @since 0.1.141
|
|
78
78
|
*/
|
|
79
79
|
derivedChanges?: TSyncDerivedChange[];
|
|
80
|
+
/**
|
|
81
|
+
* Text columns of the atscript-db ≤ 0.1.154 union layout whose JSON values
|
|
82
|
+
* the run copies into the columns that replace them, before the old
|
|
83
|
+
* column is dropped (see `planJsonColumnMigration`).
|
|
84
|
+
* @since 0.1.155
|
|
85
|
+
*/
|
|
86
|
+
jsonCopies?: Array<{
|
|
87
|
+
from: string;
|
|
88
|
+
to: string[];
|
|
89
|
+
}>;
|
|
90
|
+
/**
|
|
91
|
+
* Text columns of that layout becoming JSON columns: their plain-text
|
|
92
|
+
* values are rewritten as JSON strings first.
|
|
93
|
+
* @since 0.1.155
|
|
94
|
+
*/
|
|
95
|
+
jsonified?: string[];
|
|
80
96
|
columnsAdded?: string[];
|
|
81
97
|
columnsRenamed?: string[];
|
|
82
98
|
columnsDropped?: string[];
|
|
@@ -85,14 +101,17 @@ interface TSyncEntryInit {
|
|
|
85
101
|
renamedFrom?: string;
|
|
86
102
|
/**
|
|
87
103
|
* The table's primary-key field set changes. `rebuild: true` when sync
|
|
88
|
-
* rebuilds the key
|
|
89
|
-
* (
|
|
104
|
+
* rebuilds the key; `false` when the rebuild is skipped (safe mode).
|
|
105
|
+
* `populated: true` (since 0.1.155) when the table has rows — they satisfy
|
|
106
|
+
* the new key; a populated table whose rows do not is refused instead (see
|
|
107
|
+
* `refused`).
|
|
90
108
|
* @since 0.1.128
|
|
91
109
|
*/
|
|
92
110
|
pkChange?: {
|
|
93
111
|
from: string[];
|
|
94
112
|
to: string[];
|
|
95
113
|
rebuild: boolean;
|
|
114
|
+
populated?: boolean;
|
|
96
115
|
};
|
|
97
116
|
/**
|
|
98
117
|
* The work safe mode skipped on this table: `"pk-rebuild"` (`pkChange`
|
|
@@ -174,6 +193,13 @@ declare class SyncEntry {
|
|
|
174
193
|
}>;
|
|
175
194
|
/** @since 0.1.141 — see {@link TSyncEntryInit.derivedChanges}. */
|
|
176
195
|
readonly derivedChanges: TSyncDerivedChange[];
|
|
196
|
+
/** @since 0.1.155 — see {@link TSyncEntryInit.jsonCopies}. */
|
|
197
|
+
readonly jsonCopies: Array<{
|
|
198
|
+
from: string;
|
|
199
|
+
to: string[];
|
|
200
|
+
}>;
|
|
201
|
+
/** @since 0.1.155 — see {@link TSyncEntryInit.jsonified}. */
|
|
202
|
+
readonly jsonified: string[];
|
|
177
203
|
readonly columnsAdded: string[];
|
|
178
204
|
readonly columnsRenamed: string[];
|
|
179
205
|
readonly columnsDropped: string[];
|
|
@@ -185,6 +211,7 @@ declare class SyncEntry {
|
|
|
185
211
|
from: string[];
|
|
186
212
|
to: string[];
|
|
187
213
|
rebuild: boolean;
|
|
214
|
+
populated?: boolean;
|
|
188
215
|
};
|
|
189
216
|
/** @since 0.1.128 — see {@link TSyncEntryInit.skipped}. */
|
|
190
217
|
readonly skipped: ReadonlyArray<TSyncSkippedWork>;
|
|
@@ -229,7 +256,7 @@ declare class SyncEntry {
|
|
|
229
256
|
print(mode: "plan" | "result", colors?: TSyncColors): string[];
|
|
230
257
|
private labelAndPrefix;
|
|
231
258
|
private printError;
|
|
232
|
-
/** `! PK (id) → (code) — rebuild (table is empty)` / `— skipped (safe mode)` */
|
|
259
|
+
/** `! PK (id) → (code) — rebuild (table is empty)` / `— rebuild (rows satisfy the new key)` / `— skipped (safe mode)` */
|
|
233
260
|
private printPkChange;
|
|
234
261
|
/**
|
|
235
262
|
* `! col: t1 → t2 — drop` (plan), or `! type col (t1 → t2) — skipped (safe
|
|
@@ -241,6 +268,8 @@ declare class SyncEntry {
|
|
|
241
268
|
* `— rebuilt` (result), or `! … — skipped (safe mode)` when pending.
|
|
242
269
|
*/
|
|
243
270
|
private printDerivedChanges;
|
|
271
|
+
/** `~ addr → addr__street, addr__zip — copy JSON values` / `~ extra — text values as JSON`. */
|
|
272
|
+
private printJsonMigration;
|
|
244
273
|
/** `~ col — nullable` / `~ col — default a → b`, `— skipped (safe mode)` when pending. */
|
|
245
274
|
private printNullableDefaults;
|
|
246
275
|
/**
|