@atscript/db 0.1.154 → 0.1.155
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-jy8BfswO.d.cts → buckets-DcWm3SLt.d.cts} +120 -10
- package/dist/{buckets-CBKL_ZWE.d.mts → buckets-goCoky8v.d.mts} +120 -10
- package/dist/{column-diff-DAIAq6XR.mjs → column-diff-C68V_eYM.mjs} +346 -65
- package/dist/{column-diff-qnzx1o3c.cjs → column-diff-DMkGYAGM.cjs} +357 -64
- package/dist/{fk-diff-BIJ8l_hF.d.mts → fk-diff-BYLNWoF9.d.mts} +2 -1
- package/dist/{fk-diff-D1mrLMjU.d.cts → fk-diff-DblOPBLS.d.cts} +2 -1
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +10 -5
- package/dist/index.d.mts +10 -5
- package/dist/index.mjs +2 -2
- package/dist/plugin.cjs +3 -3
- package/dist/plugin.mjs +3 -3
- package/dist/rel.d.cts +2 -2
- package/dist/rel.d.mts +2 -2
- package/dist/{relation-helpers-BUsxNNGU.d.mts → relation-helpers-B9MVBrtx.d.mts} +1 -1
- package/dist/{relation-helpers-D_8bydc2.d.cts → relation-helpers-D9kMFfum.d.cts} +1 -1
- 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 +288 -30
- package/dist/sync.d.cts +34 -5
- package/dist/sync.d.mts +34 -5
- package/dist/sync.mjs +288 -30
- package/dist/{validation-utils-DqZAH6aT.mjs → validation-utils-DxHIqa36.mjs} +23 -5
- package/dist/{validation-utils-debIYpq0.cjs → validation-utils-_PA7iSLm.cjs} +28 -4
- package/dist/{validator-BACewoRj.mjs → validator-BC3nwFi4.mjs} +178 -10
- package/dist/{validator-BxWwh3wf.cjs → validator-DHwSyBej.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-DMkGYAGM.cjs");
|
|
4
|
+
const require_validator = require("./validator-DHwSyBej.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
|
|
@@ -759,7 +958,10 @@ async function executeSyncTable(facts, safe, deps, exec) {
|
|
|
759
958
|
const existed = adapter.tableExists ? await adapter.tableExists() : false;
|
|
760
959
|
await adapter.ensureTable(ensureOpts);
|
|
761
960
|
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), {
|
|
961
|
+
} else await applyColumnDiff(adapter, readable, facts.diff ?? require_column_diff.computeColumnDiff(readable.columnDescriptors, require_column_diff.snapshotToExistingColumns(storedSnapshot, readable), deps.resolveTypeMapper(adapter), {
|
|
962
|
+
snapshot: storedSnapshot,
|
|
963
|
+
nativeDefaultFns: adapter.nativeDefaultFns()
|
|
964
|
+
}), init, safe, deps.logger, ensureOpts);
|
|
763
965
|
else {
|
|
764
966
|
const existed = adapter.tableExists ? await adapter.tableExists() : true;
|
|
765
967
|
if (!init.recreated) {
|
|
@@ -901,6 +1103,13 @@ function willDropRecreate(readable, diff, optionDiff) {
|
|
|
901
1103
|
if (readable.syncMethod === "drop" && (diff?.typeChanged.length ?? 0) > 0) return true;
|
|
902
1104
|
return (optionDiff?.changed.some((c) => c.destructive) ?? false) && !(readable.syncMethod === "recreate" && !!adapter.recreateTable);
|
|
903
1105
|
}
|
|
1106
|
+
/**
|
|
1107
|
+
* The column diff the executor applies after the table's JSON column copy —
|
|
1108
|
+
* its targets' NOT NULL and defaults deferred (see {@link diffAfterJsonCopy}).
|
|
1109
|
+
*/
|
|
1110
|
+
function afterJsonCopy(facts) {
|
|
1111
|
+
return facts.diff && facts.jsonMigration ? diffAfterJsonCopy(facts.jsonMigration, facts.diff) : facts.diff;
|
|
1112
|
+
}
|
|
904
1113
|
/** Records work safe mode skipped on the entry (see `SyncEntry.skipped`). */
|
|
905
1114
|
function markSkipped(init, kind) {
|
|
906
1115
|
init.skipped = [...init.skipped ?? [], kind];
|
|
@@ -918,7 +1127,8 @@ function needsDdlForNullableDefaults(adapter) {
|
|
|
918
1127
|
* DDL either way.
|
|
919
1128
|
*/
|
|
920
1129
|
function safeModeSkips(facts) {
|
|
921
|
-
const { readable,
|
|
1130
|
+
const { readable, optionDiff, fkDiff } = facts;
|
|
1131
|
+
const diff = afterJsonCopy(facts);
|
|
922
1132
|
const adapter = readable.dbAdapter;
|
|
923
1133
|
const out = [];
|
|
924
1134
|
if (facts.planEntry.status === "error") return out;
|
|
@@ -932,6 +1142,16 @@ function safeModeSkips(facts) {
|
|
|
932
1142
|
if (optionDiff?.changed.some((c) => c.destructive)) out.push("table-options");
|
|
933
1143
|
return out;
|
|
934
1144
|
}
|
|
1145
|
+
/** The plan-shaped summary of a {@link TJsonColumnMigration}. */
|
|
1146
|
+
function describeJsonMigration(migration) {
|
|
1147
|
+
return {
|
|
1148
|
+
jsonCopies: migration.copies.length > 0 ? migration.copies.map((c) => ({
|
|
1149
|
+
from: c.source,
|
|
1150
|
+
to: c.targets.map((t) => t.column)
|
|
1151
|
+
})) : void 0,
|
|
1152
|
+
jsonified: migration.jsonify.length > 0 ? migration.jsonify : void 0
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
935
1155
|
/** The plan-shaped nullable / default change summaries of a diff. */
|
|
936
1156
|
function describeNullableDefaults(diff) {
|
|
937
1157
|
return {
|
|
@@ -963,6 +1183,28 @@ async function dropAndRecreate(adapter, init, name, ensureOpts) {
|
|
|
963
1183
|
init.recreated = true;
|
|
964
1184
|
init.status = "alter";
|
|
965
1185
|
}
|
|
1186
|
+
/**
|
|
1187
|
+
* `recreateTable()` before the column diff is applied: it copies the rows by
|
|
1188
|
+
* column name, so the columns renamed in this run are renamed first — under
|
|
1189
|
+
* the old name their values would not be copied.
|
|
1190
|
+
*/
|
|
1191
|
+
async function recreateKeepingRenames(adapter, diff, init) {
|
|
1192
|
+
if (diff.renamed.length > 0 && adapter.syncColumns) {
|
|
1193
|
+
const { renamed } = await adapter.syncColumns({
|
|
1194
|
+
added: [],
|
|
1195
|
+
removed: [],
|
|
1196
|
+
renamed: diff.renamed,
|
|
1197
|
+
typeChanged: [],
|
|
1198
|
+
nullableChanged: [],
|
|
1199
|
+
defaultChanged: [],
|
|
1200
|
+
conflicts: []
|
|
1201
|
+
});
|
|
1202
|
+
init.columnsRenamed = renamed;
|
|
1203
|
+
}
|
|
1204
|
+
await adapter.recreateTable();
|
|
1205
|
+
init.recreated = true;
|
|
1206
|
+
init.status = "alter";
|
|
1207
|
+
}
|
|
966
1208
|
async function applyColumnDiff(adapter, readable, diff, init, safe, logger, ensureOpts) {
|
|
967
1209
|
const name = readable.tableName;
|
|
968
1210
|
if (diff.conflicts.length > 0) {
|
|
@@ -981,11 +1223,8 @@ async function applyColumnDiff(adapter, readable, diff, init, safe, logger, ensu
|
|
|
981
1223
|
const cols = init.typeChanges.map((tc) => `${tc.column}: ${tc.fromType} → ${tc.toType}`).join(", ");
|
|
982
1224
|
logger.warn?.(`[schema-sync] Type change on "${name}" (${cols}) — drop-and-recreate skipped (safe mode)`);
|
|
983
1225
|
} 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") {
|
|
1226
|
+
else if (strategy === "recreate") await recreateKeepingRenames(adapter, diff, init);
|
|
1227
|
+
else if (strategy === "modify") {
|
|
989
1228
|
needsSyncColumns = true;
|
|
990
1229
|
init.status = "alter";
|
|
991
1230
|
} else {
|
|
@@ -999,11 +1238,8 @@ async function applyColumnDiff(adapter, readable, diff, init, safe, logger, ensu
|
|
|
999
1238
|
if (!safe && !init.recreated && init.status !== "error") if (adapter.supportsColumnModify && adapter.syncColumns) {
|
|
1000
1239
|
needsSyncColumns = true;
|
|
1001
1240
|
init.status = "alter";
|
|
1002
|
-
} else if (adapter.recreateTable)
|
|
1003
|
-
|
|
1004
|
-
init.recreated = true;
|
|
1005
|
-
init.status = "alter";
|
|
1006
|
-
} else init.status = "alter";
|
|
1241
|
+
} else if (adapter.recreateTable) await recreateKeepingRenames(adapter, diff, init);
|
|
1242
|
+
else init.status = "alter";
|
|
1007
1243
|
else if (init.status !== "error") {
|
|
1008
1244
|
if (safe && !init.recreated && needsDdlForNullableDefaults(adapter)) {
|
|
1009
1245
|
Object.assign(init, describeNullableDefaults(diff));
|
|
@@ -1015,7 +1251,7 @@ async function applyColumnDiff(adapter, readable, diff, init, safe, logger, ensu
|
|
|
1015
1251
|
}
|
|
1016
1252
|
if (!init.recreated && init.status !== "error" && (diff.added.length > 0 || diff.renamed.length > 0 || needsSyncColumns) && adapter.syncColumns) {
|
|
1017
1253
|
const syncResult = await adapter.syncColumns(diff);
|
|
1018
|
-
init.columnsAdded = syncResult.added;
|
|
1254
|
+
init.columnsAdded = [...init.columnsAdded ?? [], ...syncResult.added];
|
|
1019
1255
|
init.columnsRenamed = syncResult.renamed;
|
|
1020
1256
|
if (syncResult.added.length > 0 || (syncResult.renamed?.length ?? 0) > 0 || needsSyncColumns) init.status = "alter";
|
|
1021
1257
|
}
|
|
@@ -1580,8 +1816,18 @@ var SchemaSync = class {
|
|
|
1580
1816
|
init.columnsToAdd = [...readable.columnDescriptors];
|
|
1581
1817
|
} else if (facts.existing && facts.existing.length > 0) {
|
|
1582
1818
|
const typeMapper = adapter.typeMapper?.bind(adapter);
|
|
1583
|
-
facts.diff = require_column_diff.computeColumnDiff(readable.columnDescriptors, facts.existing, typeMapper, {
|
|
1819
|
+
facts.diff = require_column_diff.computeColumnDiff(readable.columnDescriptors, facts.existing, typeMapper, {
|
|
1820
|
+
snapshot: storedSnapshot,
|
|
1821
|
+
nativeDefaultFns: adapter.nativeDefaultFns()
|
|
1822
|
+
});
|
|
1584
1823
|
this.populatePlanFromDiff(facts.diff, init, readable, safe);
|
|
1824
|
+
const migration = planJsonColumnMigration(readable, facts.diff, storedSnapshot);
|
|
1825
|
+
facts.jsonMigration = migration;
|
|
1826
|
+
Object.assign(init, describeJsonMigration(migration));
|
|
1827
|
+
if (migration.errors.length > 0) {
|
|
1828
|
+
init.status = "error";
|
|
1829
|
+
init.errors = [...init.errors ?? [], ...migration.errors];
|
|
1830
|
+
} else if (init.status === "in-sync" && (migration.copies.length > 0 || migration.jsonify.length > 0)) init.status = "alter";
|
|
1585
1831
|
}
|
|
1586
1832
|
} else if (adapter.syncColumns) if (!storedSnapshot) {
|
|
1587
1833
|
if (!pendingRename) {
|
|
@@ -1592,7 +1838,10 @@ var SchemaSync = class {
|
|
|
1592
1838
|
}
|
|
1593
1839
|
} else {
|
|
1594
1840
|
const existing = require_column_diff.snapshotToExistingColumns(storedSnapshot, readable);
|
|
1595
|
-
facts.diff = require_column_diff.computeColumnDiff(readable.columnDescriptors, existing, this.resolveTypeMapper(adapter), {
|
|
1841
|
+
facts.diff = require_column_diff.computeColumnDiff(readable.columnDescriptors, existing, this.resolveTypeMapper(adapter), {
|
|
1842
|
+
snapshot: storedSnapshot,
|
|
1843
|
+
nativeDefaultFns: adapter.nativeDefaultFns()
|
|
1844
|
+
});
|
|
1596
1845
|
this.populatePlanFromDiff(facts.diff, init, readable, safe);
|
|
1597
1846
|
}
|
|
1598
1847
|
else if (adapter.tableExists) {
|
|
@@ -1627,7 +1876,16 @@ var SchemaSync = class {
|
|
|
1627
1876
|
}, fk.existing)
|
|
1628
1877
|
}));
|
|
1629
1878
|
}
|
|
1630
|
-
if (facts.diff?.primaryKeyChanged)
|
|
1879
|
+
if (facts.diff?.primaryKeyChanged) {
|
|
1880
|
+
const tableName = pendingRename ? dbName : void 0;
|
|
1881
|
+
facts.populated = await adapter.hasRows(tableName) ?? "unknown";
|
|
1882
|
+
const { added, renamed } = facts.diff;
|
|
1883
|
+
const { to } = facts.diff.primaryKeyChanged;
|
|
1884
|
+
if (facts.populated === true && adapter.countKeyViolations && !added.some((f) => to.includes(f.physicalName))) {
|
|
1885
|
+
const liveName = new Map(renamed.map((r) => [r.field.physicalName, r.oldName]));
|
|
1886
|
+
facts.keyViolations = await adapter.countKeyViolations(to.map((c) => liveName.get(c) ?? c), tableName);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1631
1889
|
facts.dropsTable = !!facts.diff?.primaryKeyChanged || willDropRecreate(readable, facts.diff, facts.optionDiff);
|
|
1632
1890
|
if (facts.diff?.primaryKeyChanged || probeRecreateInbound && facts.dropsTable) facts.inboundFks = await adapter.getReferencingForeignKeys?.(dbName);
|
|
1633
1891
|
if (adapter.getObjectKind) facts.objectKind = await adapter.getObjectKind(dbName);
|
|
@@ -1644,7 +1902,7 @@ var SchemaSync = class {
|
|
|
1644
1902
|
const pk = t.diff?.primaryKeyChanged;
|
|
1645
1903
|
if (pk) {
|
|
1646
1904
|
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.`);
|
|
1905
|
+
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
1906
|
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
1907
|
if (!safe) {
|
|
1650
1908
|
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, It as TTableOptionDiff, U as TDbFieldMeta, et as TDbObjectKind, gt as TEnsureTableOptions, jt as TReferencingForeignKey, mt as TDerivedChangeReason, qn as TGenericLogger, tn as DbSpace, vt as TExistingForeignKey, yt as TExistingTableOption } from "./buckets-DcWm3SLt.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-DblOPBLS.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
|
/**
|
package/dist/sync.d.mts
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, It as TTableOptionDiff, U as TDbFieldMeta, et as TDbObjectKind, gt as TEnsureTableOptions, jt as TReferencingForeignKey, mt as TDerivedChangeReason, qn as TGenericLogger, tn as DbSpace, vt as TExistingForeignKey, yt as TExistingTableOption } from "./buckets-goCoky8v.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-BYLNWoF9.mjs";
|
|
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
|
/**
|