@metaobjectsdev/migrate-ts 0.21.4 → 0.21.6
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/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -0
- package/dist/errors.js.map +1 -1
- package/dist/referential-actions.d.ts +20 -6
- package/dist/referential-actions.d.ts.map +1 -1
- package/dist/referential-actions.js +136 -21
- package/dist/referential-actions.js.map +1 -1
- package/package.json +3 -3
- package/src/errors.ts +4 -0
- package/src/referential-actions.ts +138 -20
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,YAAY,CAAC;AAMrD;;;;;;;;;GASG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,SAAkB,IAAI,6BAA6B;IACnD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,EAAE,CAAC;gBAEvB,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE;CAWlF;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,YAAY,CAAC;AAMrD;;;;;;;;;GASG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,SAAkB,IAAI,6BAA6B;IACnD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,EAAE,CAAC;gBAEvB,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE;CAWlF;AAkDD,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,SAAkB,IAAI,yBAAyB;IAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC;gBAEnB,OAAO,EAAE,MAAM,EAAE;CAU9B;AAED;;;;;;;;;GASG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,SAAkB,IAAI,2BAA2B;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEb,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM;CAenG"}
|
package/dist/errors.js
CHANGED
|
@@ -33,6 +33,10 @@ const ENABLE_FLAG_BY_KIND = {
|
|
|
33
33
|
"change-column-nullable": "allow.nullableToNotNull",
|
|
34
34
|
"drop-index": "allow.dropIndex",
|
|
35
35
|
"drop-fk": "allow.dropFk",
|
|
36
|
+
// change-column-default is blocked in exactly one shape — dropping a live
|
|
37
|
+
// Postgres auto-sequence default with no declared @generation (see
|
|
38
|
+
// diff/status.ts) — and allow.dropIdentityDefault is the flag that enables it.
|
|
39
|
+
"change-column-default": "allow.dropIdentityDefault",
|
|
36
40
|
};
|
|
37
41
|
function changeLocator(c) {
|
|
38
42
|
switch (c.kind) {
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,8EAA8E;AAC9E,4DAA4D;AAC5D,8EAA8E;AAE9E;;;;;;;;;GASG;AACH,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAC9B,IAAI,GAAG,yBAAyB,CAAC;IAC1C,UAAU,CAAS;IACnB,cAAc,CAAS;IACvB,eAAe,CAAW;IAEnC,YAAY,UAAkB,EAAE,cAAsB,EAAE,eAAyB;QAC/E,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7C,KAAK,CACH,WAAW,UAAU,qBAAqB,cAAc,4BAA4B;YACpF,iBAAiB,SAAS,oCAAoC;YAC9D,uHAAuH,CACxH,CAAC;QACF,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACrC,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;IACzC,CAAC;CACF;AAED,MAAM,mBAAmB,GAAwC;IAC/D,aAAa,EAAE,kBAAkB;IACjC,YAAY,EAAE,iBAAiB;IAC/B,oBAAoB,EAAE,kBAAkB;IACxC,wBAAwB,EAAE,yBAAyB;IACnD,YAAY,EAAE,iBAAiB;IAC/B,SAAS,EAAE,cAAc;
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,13 +10,27 @@ export declare function isRequired(field: MetaData): boolean;
|
|
|
10
10
|
* Precedence (highest first):
|
|
11
11
|
* 1. @onDelete / @onUpdate declared DIRECTLY on the identity.reference — the
|
|
12
12
|
* reference IS the FK, so the action may be declared right where the FK is.
|
|
13
|
-
* 2. A correlated sibling relationship on the same entity
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* 2. A correlated sibling relationship on the same entity — matched
|
|
14
|
+
* package-aware against the resolved @references target (refMatchesObject
|
|
15
|
+
* / ADR-0042, so bare and FQN forms pair correctly); an M:N relationship
|
|
16
|
+
* (@through) never correlates with a direct FK. Its explicit @onDelete,
|
|
17
|
+
* else its subtype default (composition→cascade, aggregation→set-null,
|
|
18
|
+
* association→restrict); onUpdate defaults to "cascade".
|
|
19
|
+
* 3. A correlated REVERSE relationship on the TARGET entity — the documented
|
|
20
|
+
* parent-side authoring shape ("Program owns weeks": composition declared
|
|
21
|
+
* on the parent with @objectRef back at this FK-owning entity). Same
|
|
22
|
+
* explicit-action-else-subtype-default resolution as tier 2. Guards:
|
|
23
|
+
* an M:N relationship (@through) never correlates (it describes the
|
|
24
|
+
* junction path, not this direct FK); when the FK-owning entity holds
|
|
25
|
+
* MORE THAN ONE enforced reference to the same target the reverse
|
|
26
|
+
* relationship contributes nothing (it cannot say which FK carries the
|
|
27
|
+
* ownership edge — arming all of them could cascade through an edge the
|
|
28
|
+
* author never designated; fail closed); and an INFERRED set-null default
|
|
29
|
+
* (parent-side aggregation, no explicit @onDelete) on a NOT NULL FK drops
|
|
30
|
+
* the INFERRED contributions only — an authored @onUpdate on that same
|
|
31
|
+
* relationship still applies (see the in-body guard comment).
|
|
32
|
+
* 4. None → undefined (no ON DELETE / ON UPDATE clause).
|
|
18
33
|
*
|
|
19
|
-
* - "setnull" is accepted as an alias for the canonical "set-null".
|
|
20
34
|
* - Resolved "no-action" → undefined: introspection in introspect/{postgres,sqlite}.ts
|
|
21
35
|
* omits actions when the DB value is "no-action", so the expected side does the same
|
|
22
36
|
* to keep round-trip diffs clean.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"referential-actions.d.ts","sourceRoot":"","sources":["../src/referential-actions.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"referential-actions.d.ts","sourceRoot":"","sources":["../src/referential-actions.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,UAAU,EAEf,KAAK,qBAAqB,EAC1B,KAAK,QAAQ,EACd,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAO3C,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,EAAE,CAO/D;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAKhF;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAOnD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,qBAAqB,GACzB;IAAE,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;IAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAA;CAAE,CAiFpE;AAkED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,qBAAqB,EAC1B,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAC9B,cAAc,EAAE,MAAM,GACrB,IAAI,CAeN"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ON_DELETE_DEFAULT_BY_SUBTYPE, ON_UPDATE_DEFAULT, FIELD_ATTR_REQUIRED, IDENTITY_ATTR_FIELDS, TYPE_VALIDATOR, VALIDATOR_SUBTYPE_REQUIRED, } from "@metaobjectsdev/metadata";
|
|
1
|
+
import { ON_DELETE_DEFAULT_BY_SUBTYPE, ON_UPDATE_DEFAULT, FIELD_ATTR_REQUIRED, IDENTITY_ATTR_FIELDS, TYPE_VALIDATOR, VALIDATOR_SUBTYPE_REQUIRED, refMatchesObject, resolveObjectRef, } from "@metaobjectsdev/metadata";
|
|
2
2
|
import { SetNullNotNullableError } from "./errors.js";
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
// Shared field helpers — exported for use by expected-schema.ts
|
|
@@ -34,13 +34,27 @@ export function isRequired(field) {
|
|
|
34
34
|
* Precedence (highest first):
|
|
35
35
|
* 1. @onDelete / @onUpdate declared DIRECTLY on the identity.reference — the
|
|
36
36
|
* reference IS the FK, so the action may be declared right where the FK is.
|
|
37
|
-
* 2. A correlated sibling relationship on the same entity
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
37
|
+
* 2. A correlated sibling relationship on the same entity — matched
|
|
38
|
+
* package-aware against the resolved @references target (refMatchesObject
|
|
39
|
+
* / ADR-0042, so bare and FQN forms pair correctly); an M:N relationship
|
|
40
|
+
* (@through) never correlates with a direct FK. Its explicit @onDelete,
|
|
41
|
+
* else its subtype default (composition→cascade, aggregation→set-null,
|
|
42
|
+
* association→restrict); onUpdate defaults to "cascade".
|
|
43
|
+
* 3. A correlated REVERSE relationship on the TARGET entity — the documented
|
|
44
|
+
* parent-side authoring shape ("Program owns weeks": composition declared
|
|
45
|
+
* on the parent with @objectRef back at this FK-owning entity). Same
|
|
46
|
+
* explicit-action-else-subtype-default resolution as tier 2. Guards:
|
|
47
|
+
* an M:N relationship (@through) never correlates (it describes the
|
|
48
|
+
* junction path, not this direct FK); when the FK-owning entity holds
|
|
49
|
+
* MORE THAN ONE enforced reference to the same target the reverse
|
|
50
|
+
* relationship contributes nothing (it cannot say which FK carries the
|
|
51
|
+
* ownership edge — arming all of them could cascade through an edge the
|
|
52
|
+
* author never designated; fail closed); and an INFERRED set-null default
|
|
53
|
+
* (parent-side aggregation, no explicit @onDelete) on a NOT NULL FK drops
|
|
54
|
+
* the INFERRED contributions only — an authored @onUpdate on that same
|
|
55
|
+
* relationship still applies (see the in-body guard comment).
|
|
56
|
+
* 4. None → undefined (no ON DELETE / ON UPDATE clause).
|
|
42
57
|
*
|
|
43
|
-
* - "setnull" is accepted as an alias for the canonical "set-null".
|
|
44
58
|
* - Resolved "no-action" → undefined: introspection in introspect/{postgres,sqlite}.ts
|
|
45
59
|
* omits actions when the DB value is "no-action", so the expected side does the same
|
|
46
60
|
* to keep round-trip diffs clean.
|
|
@@ -58,38 +72,139 @@ export function resolveReferentialActions(entity, ref) {
|
|
|
58
72
|
const target = ref.targetEntity;
|
|
59
73
|
if (target === undefined)
|
|
60
74
|
return { onDelete: undefined, onUpdate: undefined };
|
|
61
|
-
// Correlation is by exact-string match. Every fixture in the corpus uses
|
|
62
|
-
// bare entity names for @objectRef and @references (no `::`-FQN form), so
|
|
63
|
-
// bare-vs-bare matching is sufficient today. If a future author writes an
|
|
64
|
-
// FQN value on either side, this find returns undefined and both actions
|
|
65
|
-
// resolve to undefined (no clause emitted) — surfacing the mismatch as a
|
|
66
|
-
// silent loss of intent rather than a wrong action. Cross-language ports
|
|
67
|
-
// should match the same correlation rule.
|
|
68
75
|
// (1) Actions declared directly on the FK-defining reference win.
|
|
69
76
|
const refOnDelete = ref.onDelete;
|
|
70
77
|
const refOnUpdate = ref.onUpdate;
|
|
78
|
+
// Resolve the reference's target ONCE, package-aware (ADR-0042: a bare
|
|
79
|
+
// @references resolves in the DECLARING owner's package). Both relationship
|
|
80
|
+
// tiers then correlate against the resolved node with refMatchesObject, so a
|
|
81
|
+
// bare @references pairs correctly with an FQN @objectRef (and vice versa) —
|
|
82
|
+
// an exact-string tier 2 used to miss the FQN form, letting the parent-side
|
|
83
|
+
// tier override a child-side declaration.
|
|
84
|
+
const root = entity.parent;
|
|
85
|
+
const refOwner = ref.parent ?? entity;
|
|
86
|
+
const refOwnerPkg = refOwner.package ?? refOwner.fileDefaultPackage ?? "";
|
|
87
|
+
const targetObj = root !== undefined ? resolveObjectRef(root, target, refOwnerPkg).node : undefined;
|
|
71
88
|
// (2) Otherwise correlate with a sibling relationship and use its action /
|
|
72
89
|
// subtype default. onUpdate's "cascade" default only applies when a
|
|
73
90
|
// relationship is present, so a reference-only FK with no explicit
|
|
74
|
-
// @onUpdate emits no ON UPDATE clause.
|
|
75
|
-
|
|
91
|
+
// @onUpdate emits no ON UPDATE clause. An M:N relationship (@through)
|
|
92
|
+
// never correlates — it describes the junction path, not this direct FK.
|
|
93
|
+
// When the target does not resolve (dangling @references — normally a
|
|
94
|
+
// load error), fall back to the legacy exact-string match so behavior on
|
|
95
|
+
// partially-valid trees is unchanged.
|
|
96
|
+
// (3) Failing that, correlate the REVERSE relationship declared on the
|
|
97
|
+
// TARGET entity (the documented parent-side authoring shape).
|
|
98
|
+
let rel = entity.relationships().find((r) => {
|
|
99
|
+
if (r.through !== undefined)
|
|
100
|
+
return false;
|
|
101
|
+
const objectRef = r.objectRef;
|
|
102
|
+
if (objectRef === undefined)
|
|
103
|
+
return false;
|
|
104
|
+
if (targetObj === undefined)
|
|
105
|
+
return objectRef === target;
|
|
106
|
+
const relOwner = r.parent ?? entity;
|
|
107
|
+
const relOwnerPkg = relOwner.package ?? relOwner.fileDefaultPackage ?? "";
|
|
108
|
+
return refMatchesObject(targetObj, objectRef, relOwnerPkg);
|
|
109
|
+
});
|
|
110
|
+
// When the tier-3 satisfiability guard fires, the reverse relationship's
|
|
111
|
+
// AUTHORED @onUpdate still applies (only the inferred contributions drop).
|
|
112
|
+
let suppressedReverseOnUpdate;
|
|
113
|
+
if (rel === undefined && targetObj !== undefined) {
|
|
114
|
+
const reverse = findReverseRelationship(entity, ref, targetObj);
|
|
115
|
+
// Tier-3 satisfiability guard: an INFERRED set-null default (a parent-side
|
|
116
|
+
// aggregation with no explicit @onDelete) is unsatisfiable when any FK
|
|
117
|
+
// column is NOT NULL — SET NULL cannot fire there, and letting it through
|
|
118
|
+
// would turn a previously-valid model into a hard SetNullNotNullableError
|
|
119
|
+
// purely because the correlation got smarter. An inferred default never
|
|
120
|
+
// breaks a model: the INFERRED contributions drop (today's bare FK), while
|
|
121
|
+
// anything the author explicitly wrote survives — an EXPLICIT @onDelete:
|
|
122
|
+
// "set-null" flows through and hits the loud validateSetNullNullability
|
|
123
|
+
// error (the author asked for it), and an EXPLICIT @onUpdate is honored
|
|
124
|
+
// (silently dropping it would be the original bug again).
|
|
125
|
+
if (reverse !== undefined) {
|
|
126
|
+
const unsatisfiableInferredSetNull = reverse.onDelete === undefined &&
|
|
127
|
+
ON_DELETE_DEFAULT_BY_SUBTYPE[reverse.subType] === "set-null" &&
|
|
128
|
+
readIdentityFields(ref).some((jsName) => {
|
|
129
|
+
const field = findField(entity, jsName);
|
|
130
|
+
return field !== undefined && isRequired(field);
|
|
131
|
+
});
|
|
132
|
+
if (unsatisfiableInferredSetNull) {
|
|
133
|
+
suppressedReverseOnUpdate = reverse.onUpdate;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
rel = reverse;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
76
140
|
const onDeleteRaw = refOnDelete ??
|
|
77
141
|
(rel ? (rel.onDelete ?? ON_DELETE_DEFAULT_BY_SUBTYPE[rel.subType]) : undefined);
|
|
78
142
|
const onUpdateRaw = refOnUpdate ??
|
|
79
|
-
(rel ? (rel.onUpdate ?? ON_UPDATE_DEFAULT) :
|
|
143
|
+
(rel ? (rel.onUpdate ?? ON_UPDATE_DEFAULT) : suppressedReverseOnUpdate);
|
|
80
144
|
return {
|
|
81
145
|
onDelete: normalize(onDeleteRaw),
|
|
82
146
|
onUpdate: normalize(onUpdateRaw),
|
|
83
147
|
};
|
|
84
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Tier-3 correlation: the relationship declared on the TARGET (parent) entity
|
|
151
|
+
* pointing back at the FK-owning entity — the shape the docs and the authoring
|
|
152
|
+
* skill teach ("Author owns posts": `relationship.composition { @objectRef:
|
|
153
|
+
* "Post", @cardinality: "many" }` on Author, while Post owns the FK).
|
|
154
|
+
*
|
|
155
|
+
* Guards (each fails closed to "no contribution"):
|
|
156
|
+
* - An M:N relationship (`@through`) never correlates — it describes the
|
|
157
|
+
* junction path, not this direct FK (the junction's own FKs correlate via
|
|
158
|
+
* its own identity.reference children). The same guard applies at tier 2.
|
|
159
|
+
* - When the FK-owning entity holds more than one enforced reference to the
|
|
160
|
+
* same target, the reverse relationship cannot say WHICH FK carries the
|
|
161
|
+
* ownership edge, so it contributes to none of them (arming every FK could
|
|
162
|
+
* cascade through an edge the author never designated).
|
|
163
|
+
*
|
|
164
|
+
* If multiple reverse relationships point back at the entity (rare), the first
|
|
165
|
+
* one is used — mirroring the tier-2 sibling-relationship rule.
|
|
166
|
+
*/
|
|
167
|
+
function findReverseRelationship(entity, ref, targetObj) {
|
|
168
|
+
const root = entity.parent;
|
|
169
|
+
if (root === undefined)
|
|
170
|
+
return undefined;
|
|
171
|
+
// Ambiguity guard: exactly one enforced reference from `entity` to this
|
|
172
|
+
// target, and it must be `ref` itself.
|
|
173
|
+
const refsToTarget = entity.referenceIdentities().filter((r) => {
|
|
174
|
+
if (!r.enforce)
|
|
175
|
+
return false;
|
|
176
|
+
const t = r.targetEntity;
|
|
177
|
+
if (t === undefined)
|
|
178
|
+
return false;
|
|
179
|
+
const owner = r.parent ?? entity;
|
|
180
|
+
const ownerPkg = owner.package ?? owner.fileDefaultPackage ?? "";
|
|
181
|
+
return resolveObjectRef(root, t, ownerPkg).node === targetObj;
|
|
182
|
+
});
|
|
183
|
+
if (refsToTarget.length !== 1 || refsToTarget[0] !== ref)
|
|
184
|
+
return undefined;
|
|
185
|
+
// The reverse relationship's bare @objectRef resolves in ITS declaring
|
|
186
|
+
// owner's package (normally the target entity's own package).
|
|
187
|
+
return targetObj.relationships().find((r) => {
|
|
188
|
+
if (r.through !== undefined)
|
|
189
|
+
return false; // M:N — junction path, not this FK
|
|
190
|
+
const objectRef = r.objectRef;
|
|
191
|
+
if (objectRef === undefined)
|
|
192
|
+
return false;
|
|
193
|
+
const relOwner = r.parent ?? targetObj;
|
|
194
|
+
const relOwnerPkg = relOwner.package ?? relOwner.fileDefaultPackage ?? "";
|
|
195
|
+
return refMatchesObject(entity, objectRef, relOwnerPkg);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
85
198
|
function normalize(a) {
|
|
86
199
|
if (a === undefined)
|
|
87
200
|
return undefined;
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
201
|
+
// Values are load-validated against REFERENTIAL_ACTIONS (allowedValues on both
|
|
202
|
+
// relationship.* and — since ADR-0047 — identity.reference), so only canonical
|
|
203
|
+
// kebab-case spellings reach this point. The legacy "setnull" alias was retired
|
|
204
|
+
// with the ADR-0047 registration: it now fails load with ERR_BAD_ATTR_VALUE.
|
|
205
|
+
if (a === "no-action")
|
|
91
206
|
return undefined;
|
|
92
|
-
return
|
|
207
|
+
return a;
|
|
93
208
|
}
|
|
94
209
|
// ---------------------------------------------------------------------------
|
|
95
210
|
// Set-null / NOT NULL guard
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"referential-actions.js","sourceRoot":"","sources":["../src/referential-actions.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,EACd,0BAA0B,
|
|
1
|
+
{"version":3,"file":"referential-actions.js","sourceRoot":"","sources":["../src/referential-actions.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metaobjectsdev/migrate-ts",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.6",
|
|
4
4
|
"description": "Schema migration tooling for MetaObjects: diff metadata vs DB and emit SQL for Postgres and SQLite.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@iarna/toml": "^2.2.5",
|
|
48
|
-
"@metaobjectsdev/metadata": "0.21.
|
|
48
|
+
"@metaobjectsdev/metadata": "0.21.6"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"kysely": ">=0.27.0"
|
|
51
|
+
"kysely": ">=0.27.0 <0.30.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@libsql/kysely-libsql": "^0.4.1",
|
package/src/errors.ts
CHANGED
|
@@ -40,6 +40,10 @@ const ENABLE_FLAG_BY_KIND: Partial<Record<ChangeKind, string>> = {
|
|
|
40
40
|
"change-column-nullable": "allow.nullableToNotNull",
|
|
41
41
|
"drop-index": "allow.dropIndex",
|
|
42
42
|
"drop-fk": "allow.dropFk",
|
|
43
|
+
// change-column-default is blocked in exactly one shape — dropping a live
|
|
44
|
+
// Postgres auto-sequence default with no declared @generation (see
|
|
45
|
+
// diff/status.ts) — and allow.dropIdentityDefault is the flag that enables it.
|
|
46
|
+
"change-column-default": "allow.dropIdentityDefault",
|
|
43
47
|
};
|
|
44
48
|
|
|
45
49
|
function changeLocator(c: Change): string {
|
|
@@ -5,7 +5,10 @@ import {
|
|
|
5
5
|
IDENTITY_ATTR_FIELDS,
|
|
6
6
|
TYPE_VALIDATOR,
|
|
7
7
|
VALIDATOR_SUBTYPE_REQUIRED,
|
|
8
|
+
refMatchesObject,
|
|
9
|
+
resolveObjectRef,
|
|
8
10
|
type MetaObject,
|
|
11
|
+
type MetaRelationship,
|
|
9
12
|
type MetaReferenceIdentity,
|
|
10
13
|
type MetaData,
|
|
11
14
|
} from "@metaobjectsdev/metadata";
|
|
@@ -48,13 +51,27 @@ export function isRequired(field: MetaData): boolean {
|
|
|
48
51
|
* Precedence (highest first):
|
|
49
52
|
* 1. @onDelete / @onUpdate declared DIRECTLY on the identity.reference — the
|
|
50
53
|
* reference IS the FK, so the action may be declared right where the FK is.
|
|
51
|
-
* 2. A correlated sibling relationship on the same entity
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* 2. A correlated sibling relationship on the same entity — matched
|
|
55
|
+
* package-aware against the resolved @references target (refMatchesObject
|
|
56
|
+
* / ADR-0042, so bare and FQN forms pair correctly); an M:N relationship
|
|
57
|
+
* (@through) never correlates with a direct FK. Its explicit @onDelete,
|
|
58
|
+
* else its subtype default (composition→cascade, aggregation→set-null,
|
|
59
|
+
* association→restrict); onUpdate defaults to "cascade".
|
|
60
|
+
* 3. A correlated REVERSE relationship on the TARGET entity — the documented
|
|
61
|
+
* parent-side authoring shape ("Program owns weeks": composition declared
|
|
62
|
+
* on the parent with @objectRef back at this FK-owning entity). Same
|
|
63
|
+
* explicit-action-else-subtype-default resolution as tier 2. Guards:
|
|
64
|
+
* an M:N relationship (@through) never correlates (it describes the
|
|
65
|
+
* junction path, not this direct FK); when the FK-owning entity holds
|
|
66
|
+
* MORE THAN ONE enforced reference to the same target the reverse
|
|
67
|
+
* relationship contributes nothing (it cannot say which FK carries the
|
|
68
|
+
* ownership edge — arming all of them could cascade through an edge the
|
|
69
|
+
* author never designated; fail closed); and an INFERRED set-null default
|
|
70
|
+
* (parent-side aggregation, no explicit @onDelete) on a NOT NULL FK drops
|
|
71
|
+
* the INFERRED contributions only — an authored @onUpdate on that same
|
|
72
|
+
* relationship still applies (see the in-body guard comment).
|
|
73
|
+
* 4. None → undefined (no ON DELETE / ON UPDATE clause).
|
|
56
74
|
*
|
|
57
|
-
* - "setnull" is accepted as an alias for the canonical "set-null".
|
|
58
75
|
* - Resolved "no-action" → undefined: introspection in introspect/{postgres,sqlite}.ts
|
|
59
76
|
* omits actions when the DB value is "no-action", so the expected side does the same
|
|
60
77
|
* to keep round-trip diffs clean.
|
|
@@ -75,29 +92,78 @@ export function resolveReferentialActions(
|
|
|
75
92
|
const target = ref.targetEntity;
|
|
76
93
|
if (target === undefined) return { onDelete: undefined, onUpdate: undefined };
|
|
77
94
|
|
|
78
|
-
// Correlation is by exact-string match. Every fixture in the corpus uses
|
|
79
|
-
// bare entity names for @objectRef and @references (no `::`-FQN form), so
|
|
80
|
-
// bare-vs-bare matching is sufficient today. If a future author writes an
|
|
81
|
-
// FQN value on either side, this find returns undefined and both actions
|
|
82
|
-
// resolve to undefined (no clause emitted) — surfacing the mismatch as a
|
|
83
|
-
// silent loss of intent rather than a wrong action. Cross-language ports
|
|
84
|
-
// should match the same correlation rule.
|
|
85
95
|
// (1) Actions declared directly on the FK-defining reference win.
|
|
86
96
|
const refOnDelete = ref.onDelete;
|
|
87
97
|
const refOnUpdate = ref.onUpdate;
|
|
88
98
|
|
|
99
|
+
// Resolve the reference's target ONCE, package-aware (ADR-0042: a bare
|
|
100
|
+
// @references resolves in the DECLARING owner's package). Both relationship
|
|
101
|
+
// tiers then correlate against the resolved node with refMatchesObject, so a
|
|
102
|
+
// bare @references pairs correctly with an FQN @objectRef (and vice versa) —
|
|
103
|
+
// an exact-string tier 2 used to miss the FQN form, letting the parent-side
|
|
104
|
+
// tier override a child-side declaration.
|
|
105
|
+
const root = entity.parent;
|
|
106
|
+
const refOwner = ref.parent ?? entity;
|
|
107
|
+
const refOwnerPkg = refOwner.package ?? refOwner.fileDefaultPackage ?? "";
|
|
108
|
+
const targetObj =
|
|
109
|
+
root !== undefined ? (resolveObjectRef(root, target, refOwnerPkg).node as MetaObject | undefined) : undefined;
|
|
110
|
+
|
|
89
111
|
// (2) Otherwise correlate with a sibling relationship and use its action /
|
|
90
112
|
// subtype default. onUpdate's "cascade" default only applies when a
|
|
91
113
|
// relationship is present, so a reference-only FK with no explicit
|
|
92
|
-
// @onUpdate emits no ON UPDATE clause.
|
|
93
|
-
|
|
114
|
+
// @onUpdate emits no ON UPDATE clause. An M:N relationship (@through)
|
|
115
|
+
// never correlates — it describes the junction path, not this direct FK.
|
|
116
|
+
// When the target does not resolve (dangling @references — normally a
|
|
117
|
+
// load error), fall back to the legacy exact-string match so behavior on
|
|
118
|
+
// partially-valid trees is unchanged.
|
|
119
|
+
// (3) Failing that, correlate the REVERSE relationship declared on the
|
|
120
|
+
// TARGET entity (the documented parent-side authoring shape).
|
|
121
|
+
let rel = entity.relationships().find((r) => {
|
|
122
|
+
if (r.through !== undefined) return false;
|
|
123
|
+
const objectRef = r.objectRef;
|
|
124
|
+
if (objectRef === undefined) return false;
|
|
125
|
+
if (targetObj === undefined) return objectRef === target;
|
|
126
|
+
const relOwner = r.parent ?? entity;
|
|
127
|
+
const relOwnerPkg = relOwner.package ?? relOwner.fileDefaultPackage ?? "";
|
|
128
|
+
return refMatchesObject(targetObj, objectRef, relOwnerPkg);
|
|
129
|
+
});
|
|
130
|
+
// When the tier-3 satisfiability guard fires, the reverse relationship's
|
|
131
|
+
// AUTHORED @onUpdate still applies (only the inferred contributions drop).
|
|
132
|
+
let suppressedReverseOnUpdate: string | undefined;
|
|
133
|
+
if (rel === undefined && targetObj !== undefined) {
|
|
134
|
+
const reverse = findReverseRelationship(entity, ref, targetObj);
|
|
135
|
+
// Tier-3 satisfiability guard: an INFERRED set-null default (a parent-side
|
|
136
|
+
// aggregation with no explicit @onDelete) is unsatisfiable when any FK
|
|
137
|
+
// column is NOT NULL — SET NULL cannot fire there, and letting it through
|
|
138
|
+
// would turn a previously-valid model into a hard SetNullNotNullableError
|
|
139
|
+
// purely because the correlation got smarter. An inferred default never
|
|
140
|
+
// breaks a model: the INFERRED contributions drop (today's bare FK), while
|
|
141
|
+
// anything the author explicitly wrote survives — an EXPLICIT @onDelete:
|
|
142
|
+
// "set-null" flows through and hits the loud validateSetNullNullability
|
|
143
|
+
// error (the author asked for it), and an EXPLICIT @onUpdate is honored
|
|
144
|
+
// (silently dropping it would be the original bug again).
|
|
145
|
+
if (reverse !== undefined) {
|
|
146
|
+
const unsatisfiableInferredSetNull =
|
|
147
|
+
reverse.onDelete === undefined &&
|
|
148
|
+
ON_DELETE_DEFAULT_BY_SUBTYPE[reverse.subType] === "set-null" &&
|
|
149
|
+
readIdentityFields(ref).some((jsName) => {
|
|
150
|
+
const field = findField(entity, jsName);
|
|
151
|
+
return field !== undefined && isRequired(field);
|
|
152
|
+
});
|
|
153
|
+
if (unsatisfiableInferredSetNull) {
|
|
154
|
+
suppressedReverseOnUpdate = reverse.onUpdate;
|
|
155
|
+
} else {
|
|
156
|
+
rel = reverse;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
94
160
|
|
|
95
161
|
const onDeleteRaw =
|
|
96
162
|
refOnDelete ??
|
|
97
163
|
(rel ? (rel.onDelete ?? ON_DELETE_DEFAULT_BY_SUBTYPE[rel.subType]) : undefined);
|
|
98
164
|
const onUpdateRaw =
|
|
99
165
|
refOnUpdate ??
|
|
100
|
-
(rel ? (rel.onUpdate ?? ON_UPDATE_DEFAULT) :
|
|
166
|
+
(rel ? (rel.onUpdate ?? ON_UPDATE_DEFAULT) : suppressedReverseOnUpdate);
|
|
101
167
|
|
|
102
168
|
return {
|
|
103
169
|
onDelete: normalize(onDeleteRaw),
|
|
@@ -105,12 +171,64 @@ export function resolveReferentialActions(
|
|
|
105
171
|
};
|
|
106
172
|
}
|
|
107
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Tier-3 correlation: the relationship declared on the TARGET (parent) entity
|
|
176
|
+
* pointing back at the FK-owning entity — the shape the docs and the authoring
|
|
177
|
+
* skill teach ("Author owns posts": `relationship.composition { @objectRef:
|
|
178
|
+
* "Post", @cardinality: "many" }` on Author, while Post owns the FK).
|
|
179
|
+
*
|
|
180
|
+
* Guards (each fails closed to "no contribution"):
|
|
181
|
+
* - An M:N relationship (`@through`) never correlates — it describes the
|
|
182
|
+
* junction path, not this direct FK (the junction's own FKs correlate via
|
|
183
|
+
* its own identity.reference children). The same guard applies at tier 2.
|
|
184
|
+
* - When the FK-owning entity holds more than one enforced reference to the
|
|
185
|
+
* same target, the reverse relationship cannot say WHICH FK carries the
|
|
186
|
+
* ownership edge, so it contributes to none of them (arming every FK could
|
|
187
|
+
* cascade through an edge the author never designated).
|
|
188
|
+
*
|
|
189
|
+
* If multiple reverse relationships point back at the entity (rare), the first
|
|
190
|
+
* one is used — mirroring the tier-2 sibling-relationship rule.
|
|
191
|
+
*/
|
|
192
|
+
function findReverseRelationship(
|
|
193
|
+
entity: MetaObject,
|
|
194
|
+
ref: MetaReferenceIdentity,
|
|
195
|
+
targetObj: MetaObject,
|
|
196
|
+
): MetaRelationship | undefined {
|
|
197
|
+
const root = entity.parent;
|
|
198
|
+
if (root === undefined) return undefined;
|
|
199
|
+
|
|
200
|
+
// Ambiguity guard: exactly one enforced reference from `entity` to this
|
|
201
|
+
// target, and it must be `ref` itself.
|
|
202
|
+
const refsToTarget = entity.referenceIdentities().filter((r) => {
|
|
203
|
+
if (!r.enforce) return false;
|
|
204
|
+
const t = r.targetEntity;
|
|
205
|
+
if (t === undefined) return false;
|
|
206
|
+
const owner = r.parent ?? entity;
|
|
207
|
+
const ownerPkg = owner.package ?? owner.fileDefaultPackage ?? "";
|
|
208
|
+
return resolveObjectRef(root, t, ownerPkg).node === targetObj;
|
|
209
|
+
});
|
|
210
|
+
if (refsToTarget.length !== 1 || refsToTarget[0] !== ref) return undefined;
|
|
211
|
+
|
|
212
|
+
// The reverse relationship's bare @objectRef resolves in ITS declaring
|
|
213
|
+
// owner's package (normally the target entity's own package).
|
|
214
|
+
return targetObj.relationships().find((r) => {
|
|
215
|
+
if (r.through !== undefined) return false; // M:N — junction path, not this FK
|
|
216
|
+
const objectRef = r.objectRef;
|
|
217
|
+
if (objectRef === undefined) return false;
|
|
218
|
+
const relOwner = r.parent ?? targetObj;
|
|
219
|
+
const relOwnerPkg = relOwner.package ?? relOwner.fileDefaultPackage ?? "";
|
|
220
|
+
return refMatchesObject(entity, objectRef, relOwnerPkg);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
108
224
|
function normalize(a: string | undefined): FkAction | undefined {
|
|
109
225
|
if (a === undefined) return undefined;
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
226
|
+
// Values are load-validated against REFERENTIAL_ACTIONS (allowedValues on both
|
|
227
|
+
// relationship.* and — since ADR-0047 — identity.reference), so only canonical
|
|
228
|
+
// kebab-case spellings reach this point. The legacy "setnull" alias was retired
|
|
229
|
+
// with the ADR-0047 registration: it now fails load with ERR_BAD_ATTR_VALUE.
|
|
230
|
+
if (a === "no-action") return undefined;
|
|
231
|
+
return a as FkAction;
|
|
114
232
|
}
|
|
115
233
|
|
|
116
234
|
// ---------------------------------------------------------------------------
|