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
17 changes: 17 additions & 0 deletions .changeset/29579-reference-name-only-value.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@objectstack/spec': minor
---

spec(data): the name-only reference value a refused reader is served has a named schema, and `referenceNameVisible` says which name it carries

Clause-②: yes (widening)
Metadata-protocol: added

A `lookup` or `user` field that declares `referenceNameVisible: true` answers `$expand` with `{ id, name, readRefused: true }` for a target the reader may not read. Until now that value had no name in the spec, and the field's description named the wrong source for `name` and only some of the cases where the name is withheld.

- **New export.** `ReferenceNameOnlyValueSchema` / `ReferenceNameOnlyValue` from `@objectstack/spec/data`: a closed object of the record id the referencing row holds, a non-empty `name`, and `readRefused: true`. It is a read form only. The stored form of a reference stays the bare id, so a record write never accepts it.
- **Expanded lookup value.** `valueSchemaFor(def, 'expanded')` for the reference types lists the new schema beside the bare id and the open record object. The open record member already accepted this value, so no value parses differently than before; the new export and its authorable-surface rows are what the declaration records.
- **Which name.** The `referenceNameVisible` description now says the name is the referenced object's display name as `resolveRecordDisplayName` resolves it: its `nameField`, else its `displayNameField`, else the derived title field. Before, it named `nameField` alone.
- **When the name is withheld.** The description now lists every case where the `Record #` floor with the id is served instead of the name: the reader's field-level security keeps the name field from it; the field declares a `maskingRule`; it is a formula reading through another reference; an explicit `nameField` (or its `displayNameField` alias) points at a column that is neither title-typed nor autonumber; or no field-level answer can be had for the reader. A target with no resolvable or no non-empty name reads as the same floor.

**For consumers.** Code that types a lookup's expanded value can import `ReferenceNameOnlyValue` and check `readRefused === true` to tell a refused target from an expanded record. Show the name as text and link nothing.
19 changes: 16 additions & 3 deletions content/docs/references/data/field-value.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Field Value schema — Data Protocol reference
navTitle: Field Value
description: "Field runtime VALUE-shape contract. Reference for Address, AddressValue, CalendarDateValue, ClockTimeValue and 7 more: every property with its type and default."
description: "Field runtime VALUE-shape contract. Reference for Address, AddressValue, CalendarDateValue, ClockTimeValue and 8 more: every property with its type and default."
---

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
Expand Down Expand Up @@ -41,8 +41,8 @@ this contract has (ADR-0104 performance budget).
## TypeScript Usage

```typescript
import { AddressSchema, AddressValueSchema, CalendarDateValueSchema, ClockTimeValueSchema, FileLikeValueSchema, FileReferenceIdValueSchema, FileRefusedValueSchema, FileValueSchema, InstantValueSchema, LocationValueSchema, ReferenceIdValueSchema } from '@objectstack/spec/data';
import type { Address, AddressValue, CalendarDateValue, ClockTimeValue, FileLikeValue, FileReferenceIdValue, FileRefusedValue, FileValue, InstantValue, LocationValue, ReferenceIdValue } from '@objectstack/spec/data';
import { AddressSchema, AddressValueSchema, CalendarDateValueSchema, ClockTimeValueSchema, FileLikeValueSchema, FileReferenceIdValueSchema, FileRefusedValueSchema, FileValueSchema, InstantValueSchema, LocationValueSchema, ReferenceIdValueSchema, ReferenceNameOnlyValueSchema } from '@objectstack/spec/data';
import type { Address, AddressValue, CalendarDateValue, ClockTimeValue, FileLikeValue, FileReferenceIdValue, FileRefusedValue, FileValue, InstantValue, LocationValue, ReferenceIdValue, ReferenceNameOnlyValue } from '@objectstack/spec/data';

// Validate data
const result = AddressSchema.parse(data);
Expand Down Expand Up @@ -193,3 +193,16 @@ Type: `string`

---

## ReferenceNameOnlyValue

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **id** | `string` | ✅ | The id of the referenced record, as the referencing record holds it |
| **name** | `string` | ✅ | The referenced record's display name (its object's `nameField`, else `displayNameField`, else the derived title field), or the `Record #` floor with the id where a field-level rule on that field holds |
| **readRefused** | `true` | ✅ | The reader was refused the referenced record (the object's read gate or its row scope): only its id and display name are served, and the name links nothing. Distinct from an expanded record and from a bare id. |


---

2 changes: 1 addition & 1 deletion content/docs/references/data/field.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const result = CurrencyConfigSchema.parse(data);
| **reference** | `string` | optional | Target object name (snake_case) for lookup/master_detail fields. Required for relationship types. Used by $expand to resolve foreign key IDs into full objects. Never a stored-metadata table (`sys_metadata`, `sys_metadata_history`): their rows are served only through the metadata API, so the engine never hydrates a reference into one, and `os validate`, `os build` and the object save door refuse it. On a `tree` field it is optional and, if given, must be the declaring object's own name — the object schema refuses any other target, and writes the own name when it is left out. |
| **referenceVia** | `string` | optional | Declares this text field as the id half of a polymorphic pointer pair (ADR-0052 §5 ActivityPointer): the value is a record id of the object named by the SIBLING FIELD this key names — e.g. `record_id` with `referenceVia: 'object_name'`. The sibling must be a declared field on the same object holding an object machine name. Text fields only; mutually exclusive with `reference` (a static and a per-record target contradict). Enforced today at seed load: the value resolves as a natural key against the object the sibling column names, and an unresolvable pointer is refused loudly instead of stored verbatim. Adds no referential integrity or $expand behavior. |
| **referenceDeletedWhen** | `{ field: string; value: string }` | optional | Marks the record a `referenceVia` pointer names as PLATFORM-DELETED on rows where a sibling `select` field holds one declared option — e.g. `record_id` with `{ field: 'cancel_reason', value: 'record_deleted' }`. A renderer then draws a tombstone from that option's own label, reads nothing and links nothing. Valid only beside `referenceVia`; `field` must be a single-value `select` on the same object declaring inline `options`, and `value` one of its option values — any other shape is refused at parse. |
| **referenceNameVisible** | `boolean` | optional | Declared name exposure (`lookup` and `user` fields only). TRUE declares that a principal who can read the record carrying this field may see the referenced record's display name (the target object's `nameField` value) and id even when that principal cannot read the referenced record. Served on `$expand`: a target the reader is refused (the object's read gate or its row scope) answers `{ id, name, readRefused: true }`, per element on a multi-value field; an unexpanded read answers the stored id, as for every reader. Nothing else of the target: `$expand` on this field returns the same name and id only, the field's `displayField` does not widen it, and the name renders as text with no link to the target. A field-level rule on the name field still holds: when the reader may not read that field, it is masked, or it is a formula reading through another reference, the name is the `Record #` floor with the id. A principal who can read the target is served exactly what it is served without the key. Absent or false keeps the restricted-reference posture for a target the reader cannot read. Declared on the referencing field only; there is no target-object switch. Refused on any other type. |
| **referenceNameVisible** | `boolean` | optional | Declared name exposure (`lookup` and `user` fields only). TRUE declares that a principal who can read the record carrying this field may see the referenced record's display name (the target object's `nameField`, else its `displayNameField`, else the derived title field) and id even when that principal cannot read the referenced record. Served on `$expand`: a target the reader is refused (the object's read gate or its row scope) answers `{ id, name, readRefused: true }` (`ReferenceNameOnlyValueSchema`), per element on a multi-value field; an unexpanded read answers the stored id, as for every reader. Nothing else of the target: `$expand` on this field returns the same name and id only, the field's `displayField` does not widen it, and the name renders as text with no link to the target. A field-level rule on the name field still holds, and the name field is read only where nothing the security layer owns must be computed: the name is the `Record #` floor with the id when the reader may not read that field, it declares a `maskingRule`, it is a formula reading through another reference, an explicit `nameField` (or its `displayNameField` alias) points at a column that is neither title-typed nor autonumber, or no field-level answer can be had for the reader. A target with no resolvable or no non-empty name reads as the same floor. A principal who can read the target is served exactly what it is served without the key. Absent or false keeps the restricted-reference posture for a target the reader cannot read. Declared on the referencing field only; there is no target-object switch. Refused on any other type. |
| **mailAssociation** | `boolean` | optional | Inbound mail association (`email` fields only): TRUE marks this field as identifying a correspondent. When a mailbox service ingests a message, it associates the message with every record of this object whose value in this field equals one of the message's participant addresses (From, Sender, Reply-To, To and Cc, plus those of a forwarded message it carries; ASCII case-insensitive; the receiving mailbox's own addresses excluded; only within the mailbox's organization). A message no record matches waits in the mailbox's unassociated pool. Refused on any other type. Takes effect only where a mailbox service is composed. |
| **deleteBehavior** | `Enum<'set_null' \| 'cascade' \| 'restrict'>` | optional (default: `"set_null"`) | What happens if referenced record is deleted |
| **inlineEdit** | `boolean \| Enum<'grid' \| 'form'>` | optional | Edit these child records inline within the parent's form (atomic master-detail). true = auto-pick grid/form by child shape; 'grid' = editable line-item grid; 'form' = list + per-row full form. |
Expand Down
Loading
Loading