Тема
Query
Query — source-first описание получения данных. Он объявляет входные props, транспортный контракт и упорядоченные outputs, но не определяет, где результат будет храниться и кто станет его потребителем. Поддерживаются два варианта: kind: 'rest' и kind: 'graphql'.
В request.body, request.variables и выражениях output().from(...) доступен общий API функциональных выражений, включая типы, числа, строки, коллекции, DateTime и Duration. Специальные readers Query — prop(path), response(path?) для REST и data(path?) для GraphQL.
Полный пример
ts
defineQuery({
kind: 'rest',
props: defineProps({
filterPayload: field('Object')
.optional()
.from(filter('items-filter').output('request')),
limit: field('Number').default(100),
}),
request: {
endpoint: env('ENDPOINT_API'),
path: '/select',
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
auth: {
mode: 'profile',
profile: 'keycloak-dev',
},
timeoutMs: 15000,
formUrlencoded: false,
body: body(({ prop }) => merge(
{ limit: prop('limit') },
prop('filterPayload'),
)),
},
outputs: {
raw: output().from(response('items')),
active: output().from(
response('items')
.where(match({ active: true }))
.sortBy(get('name')),
),
},
mock: {
enabled: false,
data: null,
},
})GraphQL-вариант
GraphQL Query хранит operation отдельно от variables. Это полноценный GraphQL transport, а не REST body с текстом мутации:
ts
defineQuery({
kind: 'graphql',
props: defineProps({
leadId: field('String'),
actualTime: field('DateTime'),
}),
request: {
endpoint: env('ENDPOINT_HUB_GRAPHQL'),
operationName: 'UpdateActualTime',
document: gql`
mutation UpdateActualTime($leadId: ID!, $actualTime: DateTime!) {
updateActualTime(leadId: $leadId, actualTime: $actualTime) {
id
actualTime
}
}
`,
variables: variables(({ prop }) => ({
leadId: prop('leadId'),
actualTime: prop('actualTime'),
})),
headers: {},
auth: { mode: 'inherit' },
errorPolicy: 'throw',
},
outputs: {
updated: output().from(data('updateActualTime')),
},
mock: {
enabled: false,
data: null,
},
})gql должен быть статическим tagged template без JavaScript interpolation. Кнопка «Форматировать» форматирует одновременно Query source и GraphQL document внутри gql.
Props
defineProps задаёт единственный runtime input-контракт Query:
ts
props: defineProps({
statuses: field('String')
.array()
.optional()
.default(['active'])
.options([
{ value: 'active', label: 'Активен' },
{ value: 'closed', label: 'Закрыт' },
]),
})| API | Назначение |
|---|---|
field(type) | Тип: String, Number, Boolean, Date, Time, DateTime, Object, доменный Type или inline objectOf/recordOf |
.optional() | Поле не является обязательным |
.array() | Значение является массивом указанного типа |
.default(expression) | Значение по умолчанию |
.options([{ value, label? }]) | Статический список допустимых значений |
.vocab(identity, { valuePath, labelPath }) | Значения и подписи из Vocab |
.from(filter(identity).output(name)) | Default из output внешнего Filter |
.from(defineFilter({...}).output(name)) | Default из output inline Filter |
.options и .vocab взаимоисключающие. Нельзя одновременно задавать .default(...) и .from(...).
Вложенный объект описывается тем же рекурсивным field-синтаксисом, что и Type Source:
ts
payload: field(objectOf({
flightNumber: field(String),
route: field(objectOf({
departure: field(String),
arrival: field(String),
})),
}))Для объекта с произвольными string-ключами используется recordOf:
ts
properties: field(recordOf(objectOf({
name: field(String),
type: field(String),
text: field(String),
})))Prop участвует в запросе только при явной ссылке через prop(path) в body(...) или variables(...):
ts
body: body(({ prop }) => ({
limit: prop('limit'),
filter: prop('filterPayload'),
}))request.body и request.variables могут читать только объявленные props. Произвольного доступа к Composition, Store или глобальному окружению внутри Query source нет.
REST request
| Поле | Назначение |
|---|---|
endpoint | Базовый endpoint или Endge var-token |
path | REST path |
method | HTTP method |
headers | Статические HTTP headers |
auth | Auth-конфигурация запроса |
timeoutMs | Необязательный timeout запроса |
formUrlencoded | Кодировать body как application/x-www-form-urlencoded |
body | Безопасное выражение, построенное через body(...) |
Для auth используются те же формы, что и в Stream: mode: 'inherit', mode: 'none' или mode: 'profile' с полем profile. Значение profile — identity существующего AuthProfile:
ts
auth: {
mode: 'profile',
profile: 'keycloak-dev',
}Callback body должен непосредственно возвращать выражение. Block body, произвольный JavaScript и side effects не поддерживаются.
GraphQL request
| Поле | Назначение |
|---|---|
endpoint | GraphQL endpoint или Endge var-token |
document | Статический GraphQL document в gql tagged template |
operationName | Имя operation; обязательно, если document содержит несколько operations |
variables | Безопасное выражение, построенное через variables(...) |
headers | Дополнительные HTTP headers |
auth | Общая Auth-конфигурация Query |
timeoutMs | Необязательный timeout запроса |
errorPolicy | throw по умолчанию или ignore |
Executor отправляет POST с полями query, operationName и variables. HTTP-ошибки всегда завершают Query с ошибкой. При errorPolicy: 'throw' наличие errors в успешном HTTP-ответе также считается ошибкой; при ignore executor возвращает доступное data.
Outputs
REST output читает response, GraphQL output — поле data; оба могут ссылаться на output, объявленный выше:
ts
outputs: {
raw: output().from(response()),
items: output().from(response('data.items')),
rows: output().from('items'),
}response() возвращает весь ответ, response('items') — значение по dot-path. К reader можно применять любые общие операции:
ts
rows: output().from(
response('items')
.where(match({ active: true }))
.map(pick(['id', 'name'])),
)Для GraphQL executor сначала отделяет envelope и передаёт в output только поле data, поэтому путь не содержит дополнительный префикс:
ts
outputs: {
raw: output().from(data()),
items: output().from(data('items')),
}Ссылаться можно только на предыдущий output. Такой порядок делает граф однозначным и исключает циклы.
DataView в output
Ссылка на глобальный DataView:
ts
rows: output()
.from('raw')
.dataView('item-rows')Прежняя явная форма .dataView(dataView('item-rows')) остаётся совместимой.
Локальный DataView:
ts
rows: output()
.from('raw')
.dataView(defineDataView({
mode: 'pipeline',
steps: [
from('').as('row'),
map({
...spread('row'),
formattedDate: path('row.createdAt')
.convert('time-string-to-date'),
}),
],
}))Локальный DataView компилируется как child artifact Query и не создаёт отдельный документ домена. Полный API преобразований: DataView.
Упорядоченные преобразования output
После .from(...) DataView и Converter можно чередовать. Порядок сохраняется в Program artifact и исполняется буквально:
ts
items: output()
.from(response())
.dataView('unwrap-items')
.convert('normalize-codes', { trim: true })
.dataView('only-active')Converter получает результат предыдущего шага целиком и вызывается один раз. Legacy-поле artifact dataViews временно остаётся compatibility projection, но source и новый runtime используют единый ordered-transform список.
Mock, preview и runtime
mock.enabled переключает Query на mock.data без транспортного запроса.
Это inline mock конкретного Query, а не persisted RMock. Переиспользуемые fixtures, mock(identity), document/code-provider modes и Composition preview описаны отдельно: Mock data.
Preview компилирует Query, создаёт временный QueryRuntimeHost, выполняет запрос и показывает outputs. После отдельного запуска временный host уничтожается. В Composition host живёт вместе с графом, поддерживает повторные запуски, reactive props, отмену устаревшего HTTP-запроса и изменение outputs.
Связывание в Composition
ts
request: query('items-query').withProps({
filterPayload: fromOutput('filter', 'request'),
})Здесь выбирается значение конкретного output без внешней обёртки. fromOutput('filter') вернул бы объект всех outputs Filter, например { request: value }. Правила автоматической и ручной сборки описаны в разделе передачи props Composition.
Query не записывает данные в Store. .withProps, hooks и публикация output через .storeTo(...) являются контрактом Composition.
Настройка профилей, credentials и адаптеров описана в разделе AuthProfile.