Skip to content

Интеграции и тестирование

HTTP-адаптер (кастомный fetch / edge-окружения)

Замените встроенный клиент на axios любой HTTP-реализацией:

js
const fetchAdapter = {
  async request(config) {
    const url = `${config.baseURL ?? ''}${config.url ?? ''}`
    const res = await fetch(url, {
      method: config.method ?? 'GET',
      body: config.data ? JSON.stringify(config.data) : undefined,
      headers: { 'Content-Type': 'application/json', ...config.headers },
      signal: config.signal,
    })
    const data = await res.json()
    return {
      data,
      status: res.status,
      statusText: res.statusText,
      headers: Object.fromEntries(res.headers.entries()),
    }
  },
}

const client = createRestClient({
  baseURL: 'https://api.example.com',
  adapter: fetchAdapter,
  // auth, interceptors, sanitizeHeaders, metrics по-прежнему работают поверх адаптера
  auth: { getToken: async () => token },
})
ts
type HttpAdapter = {
  request<T = unknown>(config: RestRequestConfig & { baseURL?: string }): Promise<ApiResponse<T>>
}

Когда задан adapter, createRestClient() никогда не вызывает axios.create() — встроенный экземпляр axios просто не создаётся, так что использование только с адаптером (например, в Cloudflare Workers / Deno) не несёт за это накладных расходов.

Интеграция с Vue

vue
<script setup>
import {
  PipelineOrchestrator,
  usePipelineProgressVue,
  usePipelineRunVue,
} from 'rest-pipeline-js/vue'

const orchestrator = new PipelineOrchestrator({
  config: {
    stages: [/* ... */],
  },
})
const progress = usePipelineProgressVue(orchestrator)
const { run, running, result, error, abort, pause, resume, rerunStep } =
  usePipelineRunVue(orchestrator)
</script>

<template>
  <div>
    <div>Current stage: {{ progress.currentStage }}</div>
    <button @click="run()" :disabled="running">Start</button>
    <button @click="abort()" :disabled="!running">Abort</button>
    <button @click="pause()">Pause</button>
    <button @click="resume()">Resume</button>
    <div v-if="result">Done: {{ result }}</div>
    <div v-if="error">Error: {{ error.message }}</div>
  </div>
</template>

Composables (импорт из rest-pipeline-js/vue):

ComposableВозвращаетОписание
usePipelineProgressVue(orchestrator)Ref<PipelineProgress>Реактивный прогресс
usePipelineRunVue(orchestrator){ run, running, result, error, stageResults, abort, pause, resume, rerunStep, clearStageResults }Запуск пайплайна и получение реактивного состояния
usePipelineStepEventVue(orchestrator, stepKey, eventType)Ref<any>Последняя полезная нагрузка конкретного события шага
usePipelineLogsVue(orchestrator)Ref<log[]>Реактивные логи
useRerunPipelineStepVue(orchestrator)functionСвязанный rerunStep
useRestClientVue(config)ComputedRef<RestClient>Реактивный REST-клиент
usePipelineStageResultVue(orchestrator, stepKey)Ref<PipelineStepResult | null>Реактивный результат одного этапа

Интеграция с React

jsx
import { useRef } from 'react'
import {
  PipelineOrchestrator,
  usePipelineProgressReact,
  usePipelineRunReact,
} from 'rest-pipeline-js/react'

const orchestrator = new PipelineOrchestrator({
  config: {
    stages: [/* ... */],
  },
})

export function PipelineComponent() {
  const progress = usePipelineProgressReact(orchestrator)
  const [run, { running, result, error, abort, pause, resume, rerunStep, clearStageResults }] =
    usePipelineRunReact(orchestrator)

  return (
    <div>
      <div>Current stage: {progress.currentStage}</div>
      <button onClick={() => run()} disabled={running}>
        Start
      </button>
      <button onClick={() => abort()} disabled={!running}>
        Abort
      </button>
      <button onClick={() => pause()}>Pause</button>
      <button onClick={() => resume()}>Resume</button>
      {result && <div>Done: {JSON.stringify(result)}</div>}
      {error && <div>Error: {error.message}</div>}
    </div>
  )
}

Хуки (импорт из rest-pipeline-js/react):

ХукВозвращаетОписание
usePipelineProgressReact(orchestrator)PipelineProgressРеактивный прогресс
usePipelineRunReact(orchestrator)[run, { running, result, error, stageResults, abort, pause, resume, rerunStep, clearStageResults }]Запуск пайплайна и получение состояния
usePipelineStepEventReact(orchestrator, stepKey, eventType)anyПоследняя полезная нагрузка конкретного события шага
usePipelineLogsReact(orchestrator)log[]Реактивные логи
useRerunPipelineStepReact(orchestrator)functionСвязанный rerunStep
useRestClientReact(config)RestClientМемоизированный REST-клиент — пересоздаётся при новой ссылке на объект config; мемоизируйте его сами (useMemo/useState/константа модуля), чтобы не пересоздавать на каждом рендере
usePipelineStageResultReact(orchestrator, stepKey)PipelineStepResult | nullРезультат одного этапа

Тестирование

createMockAdapter() (из отдельной точки входа rest-pipeline-js/testing, поэтому он никогда не попадает в продакшен-бандл) заменяет сеть набором определений маршрутов — для тестирования кода, использующего createRestClient() или PipelineOrchestrator, без обращения к реальному backend:

ts
import { createRestClient } from 'rest-pipeline-js'
import { createMockAdapter } from 'rest-pipeline-js/testing'

const adapter = createMockAdapter([
  { method: 'GET', url: '/users/1', respond: { data: { id: 1, name: 'Ada' } } },

  // Динамический ответ — читает запрос, чтобы построить ответ
  {
    method: 'POST',
    url: '/orders',
    respond: (info) => ({ data: { id: 42, ...(info.data as object) }, status: 201 }),
  },

  // Последовательность ответов, по одному на каждый совпадающий вызов — проверка логики повторов:
  // первые две попытки проваливаются, третья успешна. После исчерпания повторяет последнюю запись.
  {
    method: 'GET',
    url: '/flaky',
    respond: [{ error: true, status: 503 }, { error: true, status: 503 }, { data: { ok: true } }],
  },
])

const client = createRestClient({ baseURL: 'https://api.example.com', adapter })

const user = await client.get('/users/1')
// adapter.calls — история всех обработанных запросов по порядку; проверяйте её в тестах
expect(adapter.calls).toHaveLength(1)
  • url совпадает по подстроке (string) либо через .test() (RegExp) с относительным URL (config.url, например /users/1), а не с полным URL. Опустите method, чтобы совпадать с любым методом.
  • Ответ со status >= 400 по умолчанию отклоняется (как это делают axios/fetch), выбрасывается как Error с заданными .status и .response.status/.response.data/.response.headers — этого достаточно, чтобы работали retry.retriableStatus, circuitBreaker и перехватчики ошибок. Переопределяется явным error: true/false в спецификации ответа.
  • delayMs на ответе имитирует сетевую задержку через настоящий setTimeout (нормально работает под vi.useFakeTimers()/аналогами).
  • Отсутствие маршрута, подходящего под запрос, немедленно бросает исключение с понятным сообщением, вместо зависания — тест с недостающей настройкой маршрута проваливается явно.
  • adapter.reset() очищает calls и перематывает любые массивные последовательности respond в начало, не трогая сами маршруты.

Более полный пример, включая использование с PipelineOrchestrator через httpConfig, см. в examples/mock-adapter.ts.