Skip to content

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
pathREST path
methodHTTP method
headersСтатические HTTP headers
authAuth-конфигурация запроса
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

ПолеНазначение
endpointGraphQL endpoint или Endge var-token
documentСтатический GraphQL document в gql tagged template
operationNameИмя operation; обязательно, если document содержит несколько operations
variablesБезопасное выражение, построенное через variables(...)
headersДополнительные HTTP headers
authОбщая Auth-конфигурация Query
timeoutMsНеобязательный timeout запроса
errorPolicythrow по умолчанию или 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.