You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
`FieldSchema` now rejects an authored `deleteBehavior: 'set_null'` on a `master_detail` field at parse time (#9689). The engine has always resolved every value except `restrict` on that type to `cascade`, so the declaration asked for the child rows to be kept and got them deleted — silently, at the moment the parent went away. The rejection names the outcome and both legal re-declarations (`restrict` refuses the parent delete while children exist — no data loss; `cascade`, or omitting the key, accepts the cascade deliberately; a `lookup` is the type to use when children must survive the parent).
7
+
8
+
Mechanism (the #7918 Option A shape, plus the 2026-08-24 idempotent-materialization ruling): the property-level `.default('set_null')` moved off `deleteBehavior` into a post-check `.overwrite()`, so the schema can tell an authored `set_null` from a defaulted one — and the `.overwrite()` never materializes a default the schema itself would refuse as authored. A bare `master_detail` now parses to output that OMITS `deleteBehavior` (previously the baked `set_null` was indistinguishable from an authored one by design, so parse output rejected itself on the mainline `ObjectSchema.create()` → `defineStack` re-parse — every app build with a bare `master_detail` failed). Built app artifacts stop carrying a value the schema itself refuses; the engine treats absent exactly as it treated the baked value (both cascade — measured, behavior unchanged). Every other field type keeps byte-identical output — non-reference types still carry the default at its shape position, and `set_null` on `lookup` stays legal. The inferred `Field` output type now declares `deleteBehavior` as optional (the same accepted cost as the currency `precision` relocation); at runtime a parsed field carries it on every type except `master_detail`, where absence is the honest spelling.
9
+
10
+
There is deliberately no automatic conversion (`field-master-detail-set-null-refused` in the migration registry): only the author knows whether they meant `restrict` (keep-my-children, as a refusal) or `cascade`. Stored rows carrying the refused combination keep loading and serving — registry validation is a diagnostic, not a gate — and are refused on their next authoring-path save.
11
+
12
+
`@objectstack/objectql`: the engine behavior is unchanged (an authored `set_null` on `master_detail` still cascades — the #9625 pin holds), but the coercion site now logs loudly (`error`, falling back to `warn`) when the combination reaches it via a raw registration or a pre-tightening stored row — the two populations parse-time rejection cannot catch.
Copy file name to clipboardExpand all lines: content/docs/data-modeling/field-types.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -337,7 +337,7 @@ Parent-child relationship (cascading delete by default).
337
337
|:---|:---|:---|:---|
338
338
|`reference`|`string`|**required**| Target (master) object name |
339
339
|`referenceFilters`|`string[]`| — |**Removed** (#2377, ADR-0049) — no longer a recognized field property. `FieldSchema` is a strict object, so an unknown key is **rejected with guidance**, not silently stripped (ADR-0078): the error echoes the offending key and prescribes the replacement. Use structured `lookupFilters` + `dependsOn` instead; see [Relationships](/docs/data-modeling/relationships)|
340
-
|`deleteBehavior`|`'restrict' \| 'cascade' \| 'set_null'`|`'cascade'`| Behavior when parent is deleted. `restrict`is the only value that deviates: master-detail cascades on everything else, so an explicit `set_null`here is **not**honored — the child is deleted with the parent |
340
+
|`deleteBehavior`|`'restrict' \| 'cascade'`|`'cascade'`| Behavior when parent is deleted. `restrict`refuses the parent delete while children exist; `cascade` (and omitting the key) deletes the children with the parent. An explicit `set_null` is a **parse-time rejection**on this type (#9689) — a detail row cannot outlive its master; use a `lookup` if children must survive the parent |
341
341
|`inlineEdit`|`boolean \| 'grid' \| 'form'`| — | Edit child records inline on the parent create/edit form (`true` = auto-pick, `'grid'`, or `'form'`) |
0 commit comments