Skip to content

belongs-to

Picker for a to-one relation.

Builder F.belongsTo(attribute: string): BelongsTo
Wire type key belongs-to
Class chain BelongsToRelationFieldField
Detail projection relation-entry
Column counterpart (not tabulatable)

Example

import { F, when } from '@adonia/core'

F.belongsTo('authorId')                // relation inferred: `author`
  .resource('users')                   // target inferred when omitted
  .optionLabel('fullName')
  .searchable()                        // urls.options, no inline options
  .creatable()                        // inline-create modal (D2-5)
  .required()

F.belongsTo('categoryId').preload(50).optionLabel('name').nullable()

Props

Beyond the shared field DSL:

Prop Declared on Behaviour
resource(slug: string): this RelationField Names the target resource explicitly (§7.3 { resource }). Without it the target is inferred from the Lucid relation, which is right whenever exactly one resource manages the related model.
relation(name: string): this RelationField Lucid relation this field reads and writes. Defaults to the attribute with a Id/_id suffix stripped — authorIdauthor — which is the Lucid convention for a belongsTo foreign key.
optionLabel(attribute: string): this RelationField Target attribute rendered as the option label, overriding the target’s recordTitle (§6.1).
searchable(enabled = true): this RelationField Renders the picker as an async combobox backed by the urls.options capability URL (protocol §4) instead of a static list. NO options ride the descriptor — that is the entire point: a resource with 200 000 rows must not serialize them into a form.
preload(limit: number = RELATION_OPTIONS_PAGE_SIZE): this RelationField Ships the first limit options inline as props.options while STILL emitting urls.options (protocol §4): the list renders without a round-trip, and typing past the preloaded page searches the rest.
creatable(enabled = true): this RelationField Lets the user create a target record from inside this form (§7.3), in a modal built from the TARGET resource’s own descriptor.
keyType(type: RelationKeyType): this RelationField Primary-key shape of the target model, driving the §11.1 base (vine.number() vs vine.string()). Defaults to 'number': Lucid carries no SQL type metadata on $columnsDefinitions, so the shape cannot be introspected, and increments('id') is the overwhelming convention. UUID-keyed targets declare .keyType('string').

Introspection

Read-only members this type adds — used by the compiler, the table derivation and tests rather than by a schema author.

Member Declared on Behaviour
get targetSlug(): string | undefined RelationField The declared target slug, when .resource() was called.
get relationName(): string RelationField The Lucid relation name (declared, or inferred from the attribute).
get optionLabelAttribute(): string | undefined RelationField The declared option-label attribute, when any.
get keyShape(): RelationKeyType RelationField Primary-key shape of the target (§11.1).
get preloadLimit(): number | undefined RelationField Inline-options page size, or undefined when preload() was not called.
get isSearchable(): boolean RelationField Whether the field offers server-side search (§7.3 searchable()).
get capabilities(): readonly string[] RelationField Protocol §4: options, unconditionally.
detailProps(): JsonObject RelationField Chrome plus the three props a relation-entry is defined to read (protocol §8).
get columnType(): string | undefined RelationField Derives to a relation cell only when a label attribute is declared: a table column showing raw foreign keys is noise, and the dotted key author.fullName is also what registers the relation for preloading (§18). A per-instance decision, like a choice field’s badge/text split.
get columnKey(): string RelationField author.fullName — the dotted path the derived relation cell reads (§7.5).
contributeHints(hints: EagerHints): void RelationField §18: register the relation so a detail/edit page hydrating through it reads materialized data. Without this a 40-field form with three relation fields costs three extra queries per render, and belongs-to-many hydration costs one per record.
get optionsResolver(): OptionsResolver | undefined RelationField The built-in options resolver (protocol §4), unless the author declared their own through dependentOptions() — an explicit resolver is a deliberate override of the target’s row set and must win.
resolveTarget(ctx: HttpContextLike | undefined): Promise<RelationTarget | undefined> RelationField Resolves the target once per field instance (a resource instance is per-request, so this is a per-request memo, not a global cache).
preloadedOptions(ctx: HttpContextLike): Promise<FieldOption[]> RelationField The first page of options, for preload() — the same resolution the endpoint uses, so inline and searched options are the same rows in the same order.

Validation

Vine base: vine.string(), vine.number().

§11.1: vine.number() / vine.string() matching the target’s primary key, plus the exists rule against the target’s scoped table.

Contributed by BelongsTo.vineSchema() in packages/core/src/schema/fields/belongs_to.ts. The shared modifiers (required, nullable, requiredWhen, unique, optionality) are applied on top by Field.buildValidation, and a rules() override replaces the base entirely — the validator compiler never branches on the field class.

Declared in

  • packages/core/src/schema/fields/belongs_to.tsBelongsTo
  • packages/core/src/schema/fields/relation.tsRelationField
Navigation

Type to search…

↑↓ navigate↵ selectEsc close