@atscript/db 0.1.161 → 0.1.163
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.cjs +1 -1
- package/dist/agg.d.cts +1 -1
- package/dist/agg.d.mts +1 -1
- package/dist/agg.mjs +1 -1
- package/dist/{aggregate-fns-B_sgl4mB.mjs → aggregate-fns-DsS9UQVB.mjs} +1 -1
- package/dist/{aggregate-fns-DFtKAv7Q.cjs → aggregate-fns-DupYzFD7.cjs} +1 -1
- package/dist/{buckets-B4T_alUe.d.cts → buckets-B8UOod-v.d.cts} +488 -57
- package/dist/{buckets-8mG67Mtw.d.mts → buckets-CHyspjYP.d.mts} +488 -57
- package/dist/{column-diff-DpeUbhEI.cjs → column-diff-BIGX3sRH.cjs} +853 -232
- package/dist/{column-diff-DR1glykD.mjs → column-diff-BfNIlMwX.mjs} +836 -233
- package/dist/{validator-B3xlbrWf.d.cts → db-error-B_DRL1E9.d.cts} +18 -89
- package/dist/{validator-B3xlbrWf.d.mts → db-error-B_DRL1E9.d.mts} +18 -89
- package/dist/{db-error-JcfJqqpk.cjs → db-error-DmF1_xrz.cjs} +13 -0
- package/dist/{db-error-eSsQX7rt.mjs → db-error-hraC12B9.mjs} +8 -1
- package/dist/{db-validator-plugin-BWy60OvG.d.cts → db-validator-plugin-wRqMYNBI.d.cts} +6 -0
- package/dist/{db-validator-plugin-BWy60OvG.d.mts → db-validator-plugin-wRqMYNBI.d.mts} +6 -0
- package/dist/{fk-diff-5gX42hBt.d.mts → fk-diff-BNGawZi9.d.cts} +34 -10
- package/dist/{fk-diff-BWdQ7kRZ.d.cts → fk-diff-rj9N2QLw.d.mts} +34 -10
- package/dist/index.cjs +15 -11
- package/dist/index.d.cts +24 -21
- package/dist/index.d.mts +24 -21
- package/dist/index.mjs +10 -12
- package/dist/{nested-writer-BOj_c_lI.mjs → nested-writer-ClUX0DB0.mjs} +16 -22
- package/dist/{nested-writer-Bl1SZS7x.cjs → nested-writer-yCy6wGuN.cjs} +16 -22
- package/dist/{object-CtPYTIvy.d.cts → object-Czia_qD4.d.cts} +7 -1
- package/dist/{object-CtPYTIvy.d.mts → object-Czia_qD4.d.mts} +7 -1
- package/dist/{object-Djg28csK.cjs → object-GjsuPNqo.cjs} +19 -0
- package/dist/{object-TkiJQ-Dp.mjs → object-PQ1J8jri.mjs} +14 -1
- package/dist/ops.cjs +6 -21
- package/dist/ops.mjs +6 -21
- package/dist/plugin.cjs +263 -323
- package/dist/plugin.mjs +163 -223
- package/dist/rel.cjs +2 -2
- package/dist/rel.d.cts +3 -3
- package/dist/rel.d.mts +3 -3
- package/dist/rel.mjs +2 -2
- package/dist/{relation-helpers-17_2f5hX.d.mts → relation-helpers-DloMywNs.d.cts} +1 -1
- package/dist/{relation-helpers-BLBrLQYi.d.cts → relation-helpers-zwt19E7r.d.mts} +1 -1
- package/dist/{relation-loader-DeulLrAw.cjs → relation-loader-DujL_Mh9.cjs} +1 -1
- package/dist/{relation-loader-CNBMPfRr.mjs → relation-loader-Qv3LrQ42.mjs} +1 -1
- package/dist/{search-fields-CT_UZ9fg.cjs → search-fields-B49-RCWd.cjs} +65 -1
- package/dist/{search-fields-CE8cmj9X.mjs → search-fields-BPNsxrCk.mjs} +48 -2
- package/dist/search-fields-CHqqcC3W.d.cts +30 -0
- package/dist/search-fields-CHqqcC3W.d.mts +30 -0
- package/dist/server-stamped-B6XbUQ4p.cjs +33 -0
- package/dist/server-stamped-BoP56K9M.mjs +22 -0
- package/dist/shared.cjs +42 -26
- package/dist/shared.d.cts +98 -4
- package/dist/shared.d.mts +98 -4
- package/dist/shared.mjs +4 -2
- package/dist/sync.cjs +228 -65
- package/dist/sync.d.cts +41 -5
- package/dist/sync.d.mts +41 -5
- package/dist/sync.mjs +228 -66
- package/dist/{validator-B4YDLUkj.cjs → validator-DK9Ijpo7.cjs} +3 -2
- package/dist/validator-DX97RzC2.d.cts +108 -0
- package/dist/validator-DX97RzC2.d.mts +108 -0
- package/dist/{validator-B3fPJjS1.mjs → validator-DuDWsIdQ.mjs} +3 -2
- package/dist/validator.cjs +6 -3
- package/dist/validator.d.cts +5 -4
- package/dist/validator.d.mts +5 -4
- package/dist/validator.mjs +5 -4
- package/dist/{validation-utils-BGjqc2jU.mjs → view-validation-BcgDq7M9.mjs} +364 -20
- package/dist/{validation-utils-CtLNlBtB.cjs → view-validation-BiDLCYMw.cjs} +452 -18
- package/package.json +6 -6
package/dist/sync.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { r as DbError } from "./db-error-
|
|
2
|
-
import {
|
|
3
|
-
import { f as unionValueMembers } from "./validator-
|
|
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-BfNIlMwX.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),
|
|
663
|
-
* mode)` in plan and result when
|
|
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.
|
|
755
|
-
...this.
|
|
756
|
-
...this.
|
|
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 (
|
|
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 =
|
|
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")
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
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
|
|
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 };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_db_error = require("./db-error-
|
|
1
|
+
const require_db_error = require("./db-error-DmF1_xrz.cjs");
|
|
2
2
|
const require_ops = require("./ops.cjs");
|
|
3
3
|
let _atscript_typescript_utils = require("@atscript/typescript/utils");
|
|
4
4
|
//#region src/shared/union-shape.ts
|
|
@@ -261,8 +261,9 @@ function createDbValidatorPlugin() {
|
|
|
261
261
|
const baseMeta = columnUnionBase(def)?.metadata;
|
|
262
262
|
const defaulted = !(belowAmbiguous(dbCtx, ctx.path) || ctx.root !== void 0 && pathBelowAmbiguous(ctx.root, ctx.path));
|
|
263
263
|
const has = (key) => defaulted && (meta.has(key) || baseMeta?.has(key) === true);
|
|
264
|
-
const serverManaged = has("db.default") || has("db.default.increment") || has("db.default.uuid") || has("db.default.now") || dbCtx.mode === "replace" && has("db.onUpdate.now") || meta.has("db.column.version") || meta.has("db.column.derived");
|
|
264
|
+
const serverManaged = has("db.default") || has("db.default.increment") || has("db.default.uuid") || has("db.default.now") || dbCtx.mode === "replace" && has("db.onUpdate.now") || meta.has("db.column.version") || meta.has("db.onUpdate.inc") || meta.has("db.column.derived");
|
|
265
265
|
const hasFK = meta.has("db.rel.FK");
|
|
266
|
+
if (dbCtx.keyless && meta.has("meta.id") && !ctx.path.includes(".")) return true;
|
|
266
267
|
if (serverManaged || hasFK) {
|
|
267
268
|
if (dbCtx.mode === "replace" && meta.has("meta.id") && !ctx.path.includes(".")) return;
|
|
268
269
|
return true;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { TAtscriptAnnotatedType, TAtscriptTypeArray, TAtscriptTypeObject, TValidatorPlugin, Validator } from "@atscript/typescript/utils";
|
|
2
|
+
|
|
3
|
+
//#region src/patch/patch-types.d.ts
|
|
4
|
+
interface TArrayPatch<A extends readonly unknown[]> {
|
|
5
|
+
$replace?: A;
|
|
6
|
+
$insert?: A;
|
|
7
|
+
$upsert?: A;
|
|
8
|
+
$update?: Array<Partial<TArrayElement<A>>>;
|
|
9
|
+
$remove?: Array<Partial<TArrayElement<A>>>;
|
|
10
|
+
}
|
|
11
|
+
type TArrayElement<ArrayType extends readonly unknown[]> = ArrayType extends ReadonlyArray<infer ElementType> ? ElementType : never;
|
|
12
|
+
/**
|
|
13
|
+
* Maps each property of T into a patch payload:
|
|
14
|
+
* - Array properties become `TArrayPatch<T[K]>`
|
|
15
|
+
* - Non-array properties become `Partial<T[K]>`
|
|
16
|
+
*/
|
|
17
|
+
type TDbPatch<T> = { [K in keyof T]?: T[K] extends Array<infer _> ? TArrayPatch<T[K]> : Partial<T[K]> };
|
|
18
|
+
/**
|
|
19
|
+
* Extracts `@expect.array.key` properties from an array-of-objects type.
|
|
20
|
+
* These keys uniquely identify an element inside the array and are used
|
|
21
|
+
* for `$update`, `$remove`, and `$upsert` operations.
|
|
22
|
+
*
|
|
23
|
+
* @param def - Atscript array type definition.
|
|
24
|
+
* @returns Set of property names marked as keys; empty set if none.
|
|
25
|
+
*/
|
|
26
|
+
declare function getKeyProps(def: TAtscriptAnnotatedType<TAtscriptTypeArray>): Set<string>;
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/shared/server-stamped.d.ts
|
|
29
|
+
/**
|
|
30
|
+
* The annotations whose field the server writes on every update — the write
|
|
31
|
+
* sets `@db.onUpdate.now` to the current time and increments
|
|
32
|
+
* `@db.onUpdate.inc` — so a client value is overwritten (now) or dropped
|
|
33
|
+
* (inc). Dependency-free: UI and plugin code can import it from
|
|
34
|
+
* `@atscript/db/validator` without pulling the compiler into a bundle.
|
|
35
|
+
* @since 0.1.163
|
|
36
|
+
*/
|
|
37
|
+
declare const SERVER_STAMPED_ON_UPDATE_KEYS: ReadonlySet<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Whether a field's metadata marks it as stamped by the server on every
|
|
40
|
+
* update (`@db.onUpdate.now` or `@db.onUpdate.inc`) — e.g. to render it
|
|
41
|
+
* read-only in an edit form.
|
|
42
|
+
* @since 0.1.163
|
|
43
|
+
*/
|
|
44
|
+
declare function isServerStampedOnUpdate(metadata: {
|
|
45
|
+
has(key: string): boolean;
|
|
46
|
+
}): boolean;
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/validator.d.ts
|
|
49
|
+
/** Write operation mode for validator configuration. */
|
|
50
|
+
type ValidatorMode = "insert" | "patch" | "replace";
|
|
51
|
+
/** Singleton db validator plugin — stateless, safe to share across all validators. */
|
|
52
|
+
declare const dbPlugin: TValidatorPlugin;
|
|
53
|
+
/**
|
|
54
|
+
* Builds a validator for a given write mode.
|
|
55
|
+
*
|
|
56
|
+
* Shared between server-side (`AtscriptDbTable`) and client-side (`ClientValidator`).
|
|
57
|
+
* Both use the same plugin, same `replace`, same `partial` logic.
|
|
58
|
+
*
|
|
59
|
+
* @param type - The annotated type to validate against.
|
|
60
|
+
* @param mode - The write operation mode.
|
|
61
|
+
* @param extraPlugins - Additional adapter-specific plugins (prepended before the db plugin).
|
|
62
|
+
* @param opts - `unknownProps` overrides the validator's unknown-property policy
|
|
63
|
+
* (used by db-client's `lenientWrites`, where the served type may be a
|
|
64
|
+
* projection of the full server-side type).
|
|
65
|
+
*/
|
|
66
|
+
declare function buildDbValidator(type: TAtscriptAnnotatedType, mode: ValidatorMode, extraPlugins?: TValidatorPlugin[], opts?: {
|
|
67
|
+
unknownProps?: "strip" | "ignore" | "error";
|
|
68
|
+
}): Validator<any>;
|
|
69
|
+
/**
|
|
70
|
+
* Path-aware `partial` callback for patch/update validation — the single
|
|
71
|
+
* implementation shared by the server's `bulkUpdate` validator and the
|
|
72
|
+
* client's patch preflight:
|
|
73
|
+
*
|
|
74
|
+
* - the root object is partial (a patch names only the fields it touches)
|
|
75
|
+
* - nav-relation subtrees are partial (nested data is validated by its own table)
|
|
76
|
+
* - `@db.patch.strategy "merge"` blocks are partial (the decomposer merges
|
|
77
|
+
* per leaf, so absent keys survive server-side by design)
|
|
78
|
+
*
|
|
79
|
+
* Every other nested object stays strict: non-merge blocks are `$set` as a
|
|
80
|
+
* whole (replace semantics), so their required fields really are required.
|
|
81
|
+
*/
|
|
82
|
+
declare function buildPatchPartial(navPaths: ReadonlySet<string>): (def: TAtscriptAnnotatedType<TAtscriptTypeObject>, path: string) => boolean;
|
|
83
|
+
/** Returns true if the annotated type is a navigation relation (TO/FROM/VIA). */
|
|
84
|
+
declare function isNavRelation(type: TAtscriptAnnotatedType): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Forces nav fields non-optional so the plugin handles null/undefined checks.
|
|
87
|
+
* The Validator caches replace results internally, so this allocates at most once per type node.
|
|
88
|
+
*/
|
|
89
|
+
declare function forceNavNonOptional(type: TAtscriptAnnotatedType): TAtscriptAnnotatedType;
|
|
90
|
+
/** Result of {@link buildValidationContext}. */
|
|
91
|
+
interface ValidationContext {
|
|
92
|
+
/** Flat map of dotted field paths → annotated types (same shape the server builds). */
|
|
93
|
+
flatMap: Map<string, TAtscriptAnnotatedType>;
|
|
94
|
+
/** Set of field paths that are navigation relations (TO/FROM/VIA). */
|
|
95
|
+
navFields: ReadonlySet<string>;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Builds a lightweight validation context from a deserialized Atscript type.
|
|
99
|
+
*
|
|
100
|
+
* This is the client-side equivalent of the server's `TableMetadata.build()`,
|
|
101
|
+
* without any adapter-specific processing (physical columns, index resolution, etc.).
|
|
102
|
+
*
|
|
103
|
+
* @param type - A deserialized annotated type (from `deserializeAnnotatedType(meta.type)`).
|
|
104
|
+
* @returns `flatMap` and `navFields` suitable for passing to `createDbValidatorPlugin` via `DbValidationContext`.
|
|
105
|
+
*/
|
|
106
|
+
declare function buildValidationContext(type: TAtscriptAnnotatedType<TAtscriptTypeObject>): ValidationContext;
|
|
107
|
+
//#endregion
|
|
108
|
+
export { buildValidationContext as a, isNavRelation as c, TArrayPatch as d, TDbPatch as f, buildPatchPartial as i, SERVER_STAMPED_ON_UPDATE_KEYS as l, ValidatorMode as n, dbPlugin as o, getKeyProps as p, buildDbValidator as r, forceNavNonOptional as s, ValidationContext as t, isServerStampedOnUpdate as u };
|