Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions content/docs/api/data-api.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -222,6 +222,14 @@ Delete a record.

**Response**: `{ object: "account", id: "1", success: true }`

Every relation pointing at the deleted record honours its own `deleteBehavior`
(`cascade` / `set_null` / `restrict`). On a `multiple: true` reference, `set_null`
removes just the deleted id from the array and keeps the rest, and a reference set
emptied that way reads back as `[]` — never `null`, so a client that branches on
`null` for "no link" misses the emptied case (the `multiple` doc block in
`packages/spec/src/data/field.zod.ts`, rendered in the
[Field reference](/docs/references/data/field)).

---

## Batch Operations
Expand Down
4 changes: 2 additions & 2 deletions content/docs/data-modeling/field-types.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -315,7 +315,7 @@ Reference to a record in another object (foreign key).
|:---|:---|:---|:---|
| `reference` | `string` | **required** | Target object name (snake_case) |
| `referenceFilters` | `string[]` | — | **Removed** (#2377, ADR-0049) — no longer a recognized field property (unknown keys are stripped by the schema). Use structured `lookupFilters` + `dependsOn` instead; see [Relationships](/docs/data-modeling/relationships) |
| `deleteBehavior` | `'restrict' \| 'cascade' \| 'set_null'` | `'set_null'` | Behavior when referenced record is deleted (a *required* lookup left at the default `set_null` is escalated to `restrict`, since a NOT NULL foreign key cannot be cleared) |
| `deleteBehavior` | `'restrict' \| 'cascade' \| 'set_null'` | `'set_null'` | Behavior when referenced record is deleted (a *required* lookup left at the default `set_null` is escalated to `restrict`, since a NOT NULL foreign key cannot be cleared). On a `multiple: true` lookup `set_null` removes only the deleted **member** — the other members are kept, and a set emptied that way is stored as `[]`, never `null` |

```typescript
{ name: 'company', label: 'Company', type: 'lookup', reference: 'account' }
Expand DownExpand Up@@ -655,7 +655,7 @@ These properties are available on **all** field types:
| `type` | `FieldType` | **required** | One of the supported field types |
| `required` | `boolean` | `false` | Whether the field is required |
| `unique` | `boolean` | `false` | Enforce uniqueness |
| `multiple` | `boolean` | `false` | Allow array of values |
| `multiple` | `boolean` | `false` | Allow array of values (multi-record lookup, multi-select, multi-file). An emptied multi-value lookup reads back as `[]`, never `null` — the `multiple` doc block in [`FieldSchema`](/docs/references/data/field) |
| `searchable` | `boolean` | `false` | Include in search index |
| `sortable` | `boolean` | `true` | Allow sorting by this field |
| `hidden` | `boolean` | `false` | Hide from default views |
Expand Down
10 changes: 7 additions & 3 deletions content/docs/data-modeling/fields.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -189,9 +189,13 @@ rejected with `400 VALIDATION_FAILED` and a `fields[]` entry whose `code` is
`reference_not_found` (see the [error catalog](/docs/api/error-catalog)). The
same check runs on bulk updates.

Clearing a relationship is not a dangling reference: `null`, `""` and `[]` mean
"no link" — exactly what `deleteBehavior: 'set_null'` writes when the parent
goes away.
Clearing a relationship is not a dangling reference: `null`, `""` and `[]` all mean
"no link". Which of them `deleteBehavior: 'set_null'` writes when the referenced
record goes away depends on the field: a single-value lookup is set to `null`, while
a `multiple: true` lookup keeps its other members and loses only the deleted one —
and a set emptied that way is written as `[]`, never `null` (the `multiple` doc block
in `packages/spec/src/data/field.zod.ts`, rendered in the
[Field reference](/docs/references/data/field)).

<Callout type="info">
`deleteBehavior` governs what happens to *this* record when the **referenced**
Expand Down
18 changes: 17 additions & 1 deletion content/docs/data-modeling/validation-rules.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -244,10 +244,26 @@ These properties apply to **all** field types and are validated by the base `Fie
| `reference` | `string` | — | **Required.** Target object name |
| `referenceFilters` | `string[]` | — | **Removed** (#2377, ADR-0049) — no longer part of the `Field` schema; use `lookupFilters` + `dependsOn` instead |
| `deleteBehavior` | `enum` | `set_null` | `set_null`, `cascade`, or `restrict` |
| `multiple` | `boolean` | `false` | Allow multiple references |
| `multiple` | `boolean` | `false` | Allow multiple references; the value is validated as an array of ids |

**Default constraints:** Validates that referenced record exists. Foreign key integrity enforced.

**Multi-value lookups (`multiple: true`).** The empty set is representable: an emptied
multi-value lookup is stored and read back as `[]`, never `null` — including when
`deleteBehavior: 'set_null'` removes the last remaining member. The declared contract
lives in the `multiple` and `required` doc blocks of
`packages/spec/src/data/field.zod.ts` (rendered in the
[Field reference](/docs/references/data/field)), and it makes `required` on a
multi-value lookup mean **non-empty array**.

<Callout type="warn">
The `required`-means-non-empty half is **declared but not yet enforced** by the record
validator: today `[]` passes the required check on both insert and update, because the
check treats only `undefined`, `null` and blank strings as missing. Validate emptiness
in application code until that lands (tracked in #9476). The representation guarantee
above (`[]`, never `null`) is live.
</Callout>

### `master_detail`

| Property | Type | Default | Validation Behavior |
Expand Down
9 changes: 9 additions & 0 deletions content/docs/deployment/troubleshooting.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -218,6 +218,15 @@ client.data.find('project_task', { /* query */ });
{ name: 'project', type: 'lookup', reference: 'project', deleteBehavior: 'set_null' }
```

<Callout type="info">
On a `multiple: true` lookup, `set_null` clears only the **member** that was deleted —
the child's other references survive — and a set emptied that way is stored as `[]`,
never `null` (the `multiple` doc block in `packages/spec/src/data/field.zod.ts`,
rendered in the [Field reference](/docs/references/data/field)). A cleanup query or
client branch that looks for `null` to find cleared references will not match those
rows; test for an empty array instead.
</Callout>

---

### "defineStack() validation errors"
Expand Down
13 changes: 12 additions & 1 deletion content/docs/protocol/objectql/types.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -621,10 +621,21 @@ const opportunities = await engine.find('opportunity', {
```

**On Delete Options:**
- `set_null`: Set field to null when referenced record is deleted
- `set_null`: Clear the reference when the referenced record is deleted. On a
single-value lookup the stored id becomes `null`; on a `multiple: true` lookup the
reference is a set, so only the deleted **member** is removed and the remaining
members are kept
- `restrict`: Prevent deletion if references exist
- `cascade`: Delete this record when referenced record is deleted

> **Residual shape of a multi-value reference.** A `multiple: true` lookup emptied by
> member removal is written as `[]`, **never `null`**. That is not a cascade
> convention: the representation binds every writer — cascade repair, form clears and
> API writes alike — so a reader of an array field never needs a null branch. The
> guarantee is pinned by `FieldSchema`, in the `multiple` doc block of
> `packages/spec/src/data/field.zod.ts` (rendered in the
> [Field reference](/docs/references/data/field)).

> **Required foreign keys.** A `required: true` lookup cannot be nulled, so the
> *default* `set_null` automatically escalates to `restrict` on such a field —
> deleting the parent is refused with `409 DELETE_RESTRICTED` (the response
Expand Down
Loading