References
When you write a document you regularly need the value of a status
or a data-type entry — the literal string that lives in the database.
Hardcoding 'posted' or 'user-managed' is brittle: the string can
drift, you lose editor autocomplete, and typos only surface at
runtime.
Flow Query exposes three synchronous helpers that resolve these values against the document definition at call time:
.statusRef()— returns aStatusBuilderfor the document's statuses..fieldDataType(fieldName)— returns aDataTypeBuilderfor the data-type bound to a specific field on the document..slugDataType(slug)— same, but resolved by the data-type's slug rather than by field name.
All three are synchronous — no database call, no await. They read
from the already-loaded registry.
The two canonical patterns
These two patterns show up in virtually every write-authoring hook. Commit them to muscle memory:
// Status — validated assignment.
// Throws if 'posted' is not a registered status on finance/journalItem.
status: bob
.flowQuery('finance/journalItem')
.statusRef()
.value('posted'),
// Data-type — validated assignment by field.
// Throws if 'user-managed' is not a registered value of the
// data-type bound to ledgerAccessRightsDataTypeId.
ledgerAccessRightsDataTypeId: bob
.flowQuery('finance/journalItem')
.fieldDataType('ledgerAccessRightsDataTypeId')
.value('user-managed'),
Both throw bad_request with a helpful message listing the available
values when the key is not found — so a typo fails loudly at the
point of assignment, not later during validation.
statusRef() — the StatusBuilder
const s = bob.flowQuery('purchasing/order').statusRef()
// → StatusBuilder for the 'data' status group (default)
// Or request the 'document' status group explicitly
const envelope = bob.flowQuery('purchasing/order').statusRef('document')
Value resolution
s.value('draft') // → 'draft' (throws if not registered)
s.default() // → the status with isDefault: true, e.g. 'draft'
s.firstFinalSuccess() // → first finalSuccess status, e.g. 'posted'
s.firstFinalFailure() // → first finalFailure status, e.g. 'void'
s.firstIntermediary() // → first intermediary status, e.g. 'draft'
value() is the workhorse: pass a status string, get it back if
valid, throw if not. The thrown error lists the available values so
the fix is obvious from the log.
Boolean guards
s.isImmutable('posted') // → true (document is locked)
s.isFinalSuccess('posted') // → true
s.isFinalFailure('void') // → true
s.isIntermediary('draft') // → true
s.isDefault('draft') // → true
s.has('draft') // → true (non-throwing existence check)
Listing
s.listAll() // → ['draft', 'confirmed', 'posted', 'void']
s.listFinalSuccess() // → ['posted']
s.listFinalFailure() // → ['void', 'cancelled']
s.listIntermediary() // → ['draft', 'confirmed']
s.listImmutable() // → ['posted', 'void']
Metadata and escape hatches
s.dataIndex() // → 'data.status' (the dot-path)
s.labelOf('draft') // → 'Draft' (display label)
s.definitionOf('draft') // → { value, label, type, immutable, isDefault }
s.done() // → the raw StatusGroup (escape hatch)
Guard pattern
const s = purchaseOrderFlowQuery.statusRef()
if (s.isImmutable(doc.data.status)) {
throw naoFormatErrorById('bad_request', { reason: 'Document is locked' })
}
fieldDataType(fieldName) — the DataTypeBuilder
fieldDataType resolves the data type attached to a document field
(the field's dataIndex). Use it when you're writing to that field
and want to guarantee the value is one of the registered entries.
const dt = bob
.flowQuery('purchasing/order')
.fieldDataType('purchaseOrderTypeDataTypeId')
// → DataTypeBuilder
dt.value('drop-ship') // → 'drop-ship' (throws if unknown)
dt.value('Drop Ship') // → 'drop-ship' (falls back to name match)
dt.valueByName('Drop Ship') // → 'drop-ship' (name-only)
dt.valueByKey('drop-ship') // → 'drop-ship' (exact value-only)
dt.hasValue('drop-ship') // → true (non-throwing)
dt.assertValue('drop-ship') // → this (chainable guard)
dt.listValues() // → ['purchase', 'drop-ship']
dt.listNames() // → ['Purchase', 'Drop Ship']
dt.listItems()
// → [{ name: 'Purchase', value: 'purchase' }, { name: 'Drop Ship', value: 'drop-ship' }]
dt.nameOf('drop-ship') // → 'Drop Ship' (reverse lookup)
dt.toRecord() // → { Purchase: 'purchase', 'Drop Ship': 'drop-ship' }
dt.slug() // → 'purchase-order-type'
dt.dataIndex() // → 'data.purchaseOrderTypeDataTypeId'
dt.done() // → the raw DataTypeDefinition
value() first tries an exact match on the stored key, then falls
back to a case-insensitive match on the display name. valueByKey
and valueByName are available when you need explicit single-channel
matching.
slugDataType(slug) — resolve by slug
Sometimes the data-type is shared across several fields, or the slug
is more natural to read in the call site than the field name.
slugDataType('shipping-status') resolves the same DataTypeBuilder
by slug:
// These produce equivalent builders for a field whose dataIndex uses
// the 'shipping-status' data type:
salesOrderFlowQuery.fieldDataType('shippingStatusDataTypeId')
salesOrderFlowQuery.slugDataType('shipping-status')
Choice comes down to which reads better in context:
fieldDataType('…')emphasizes "what field am I writing?"slugDataType('…')emphasizes "what enumeration am I picking from?"
Typical use sites
Building a write payload:
const data = {
id: 'JE-00042',
amount: 5000,
status: bob
.flowQuery('finance/journalItem')
.statusRef()
.value('posted'),
ledgerAccessRightsDataTypeId: bob
.flowQuery('finance/journalItem')
.fieldDataType('ledgerAccessRightsDataTypeId')
.value('user-managed'),
}
await bob.flowQuery('finance/journalItem').insertOne(data)
Default-populating a new document:
const po = bob.flowQuery('purchasing/order')
data.status = po.statusRef().default()
data.orderType = po.fieldDataType('purchaseOrderTypeDataTypeId').value('purchase')
Validating an incoming string before assignment:
const po = bob.flowQuery('purchasing/order')
po.fieldDataType('purchaseOrderTypeDataTypeId').assertValue(payload.orderType)
data.purchaseOrderTypeDataTypeId = payload.orderType
Building a dropdown:
const options = bob
.flowQuery('purchasing/order')
.fieldDataType('purchaseOrderTypeDataTypeId')
.listItems()
When not to use these
These helpers resolve values; they don't run queries. If you need to
filter by a status or data-type, use .status(...) /
.statusMutable() / .dataType(slug, ...values) on the builder
itself — see Filtering.
Next: Reading documents →