@brickflow/http
@brickflow/http
Минимальный HTTP-пакет для brickflow:
createHttpдля низкоуровневыхGET/POSTcreateUseHttpдля Nuxt state-обёртки с cache, SSR и ручнымfetchdefineGetдля типизированных endpoint-фабрикcreateUseCaseдля DI-паттерна в доменных модулях
Актуальные примеры в репозитории лежат в apps/playground.
Exports
Рекомендуемый импорт:
import { createHttp, createUseHttp, createUseCase, defineGet } from '@brickflow/http'
Из пакета также экспортируются:
createURLhashData- типы
HttpClient,HttpConfig,HttpEndpoint,HttpKey,HttpParam,HttpResponse,HttpResponseData - Nuxt-типы
UseHttpFn,UseHttpOptions,UseHttpResult
Сабпуть @brickflow/http/nuxt остаётся доступным, но в текущей кодовой базе playground использует импорты из корня.
Типизация API
Пакет не знает схему вашего API заранее. Её нужно объявить в проекте через declare module '@brickflow/http'.
Актуальный пример из apps/playground/types/http.d.ts:
import type { HttpKey, HttpResponseData } from '@brickflow/http'
type Convention = {
'/api/http-error-demo': null
'/products':
| {
limit: number
products: {
category: string
id: number
price: number
rating: number
thumbnail: string
title: string
}[]
skip: number
total: number
}
| {
message: 'error'
}
}
declare module '@brickflow/http' {
interface HttpConfig<TKey extends HttpKey = HttpKey> {
ignore?: (data: HttpResponseData<TKey>) => boolean
}
interface HttpEndpoint extends Convention {}
}
export {}
Что это даёт:
HttpKeyстановится union из endpoint-ключейhttpClient.get('/products')получает корректный тип ответаcreateGet<Params>()('/products')наследует тот же контрактHttpConfigможно расширять своими полями, как в playground черезignore
Если нужен типизированный error в UseHttpResult, дополнительно расширьте HttpErrorMap. По умолчанию error имеет тип never, а разделение между data и error зависит только от runtime isError.
createHttp
Актуальный пример из apps/playground/plugins/01.di.ts:
import { createHttp } from '@brickflow/http'
import { handlePlaygroundResponse } from '~/core/http-error'
const httpClient = createHttp({
baseURL: 'https://dummyjson.com',
headers: {
'X-Playground-Http': 'playground-runtime',
},
onResponse: handlePlaygroundResponse,
})
Поддерживаемые опции:
baseURL: stringarrayMode?: 'json' | 'repeat'fetch?: typeof fetchheaders?: Record<string, string | undefined> | (() => Record<string, string | undefined>)onResponse?: (response) => void | Promise<void>timeout?: number
GET
const response = await httpClient.get('/products', {
params: {
limit: 10,
},
retry: {
delay: 300,
retries: 3,
},
})
Особенности:
- query строится из
params GETподдерживает retry для429и5xx- delay экспоненциальный:
delay * 2 ** attempt signalможно передать как одинAbortSignalили массивAbortSignal[]
POST
const { data } = await httpClient.post('/api/http-error-demo')
Для POST:
Record<string, unknown>сериализуется в JSONFormDataотправляется как есть- retry нет
- если response не JSON, пакет вернёт пустой объект в
data
Расширение HttpConfig
Так как HttpConfig расширяемый, можно прокидывать свои поля в запрос и читать их в onResponse.
Пример из playground:
httpClient.get('/products', {
ignore: (data) => data?.limit === 20,
})
export function handlePlaygroundResponse(
response: Parameters<NonNullable<CreateHttpOptions['onResponse']>>[0],
): void {
console.log(response.config.ignore?.(response.data))
}
createUseHttp
В apps/playground/core/http.ts app-level инстанс создаётся один раз:
import { createUseHttp, defineGet } from '@brickflow/http'
import { dbDeleteKeysWithPart, dbGet, dbSafeSet } from './indexdb'
const CACHE_DB_NAME = 'smart-cache-v2'
const CACHE_STORE_NAME = 'playground-http'
const useHttp = createUseHttp({
getCache: () => ({
deleteKeysWithPart: async (part: string) => await dbDeleteKeysWithPart(part, CACHE_DB_NAME, CACHE_STORE_NAME),
get: async <T>(key: string) => await dbGet<T>(key, CACHE_DB_NAME, CACHE_STORE_NAME),
set: async <T>(key: string, value: T, ttl: number) =>
await dbSafeSet<T>(key, value, CACHE_DB_NAME, CACHE_STORE_NAME, ttl),
}),
getHttpClient: () => useNuxtApp().$http,
isDev: () => false,
})
export const createGet = defineGet(useHttp)
Зависимости:
getHttpClientобязательноgetCacheопциональноisDevопциональноisErrorопциональноttlопциональноchannelNameопционально
createUseHttp возвращает функцию useHttp(options), которая создаёт реактивное состояние запроса:
dataerrorpendingpendingCachehasFirstDatahasFreshDatafetch(params?, { signal? })
Опции useHttp
urlinitParamseffectisErrorlazyserver
effect(data, config) вызывается и для cache, и для fresh-response:
effect должен быть синхронным: async-обработчики не поддерживаются и будут отклонены TypeScript.
config.cached === trueдля cacheconfig.cached === falseдля сетиconfig.paramsсодержит уже применённые params
defineGet
defineGet связывает app-level useHttp с endpoint-фабриками:
const useHttp = createUseHttp({ ... })
export const createGet = defineGet(useHttp)
Дальше в домене можно описывать запросы коротко и типизированно:
products: createGet<{
limit: number
}>()('/products')
Path-параметры можно передавать тем же объектом params:
productOne: createGet<{
productId: number
}>()('/products/:productId')
При вызове productId будет подставлен в URL, а не уйдёт в query string:
const productHttp = await app.$di.product.productOne({
lazy: true,
})
await productHttp.fetch({
productId: 3,
})
Если generic не передан, :segment автоматически типизируется как string:
productOne: createGet()('/products/:productId')
Если generic передан, path params мерджатся с ним:
productOne: createGet<{
test: number
}>()('/products/:productId')
В этом случае тип params будет таким:
{
productId: string
test: number
}
Path params нужно передавать явно:
const productHttp = await app.$di.product.productOne({
lazy: true,
})
await productHttp.fetch({
productId: '3',
})
Если нужно явно переопределить тип ответа для конкретного endpoint-а, можно добавить ещё один generic-слой:
products: createGet<{
limit: number
}>()<{
test: string
}>('/products')
defineGet поддерживает два слоя настроек:
- Базовые настройки endpoint-а при объявлении:
const products = createGet<{ limit: number }>()('/products')
- Runtime-опции при вызове:
const productsHttp = await products({
initParams: { limit: 20 },
lazy: true,
server: false,
})
Если effect или isError указаны и при объявлении, и при вызове, пакет объединяет их так:
effectвызывает оба обработчикаisErrorиз runtime имеет приоритет
createUseCase
В playground доменный модуль собирается через createUseCase:
import { createUseCase } from '@brickflow/http'
import { createGet } from '~/core/http'
export default createUseCase()(({ httpClient }) => {
return {
async apiError() {
const { data } = await httpClient.post('/api/http-error-demo')
return data
},
products: createGet<{
limit: number
}>()('/products'),
}
})
Это даёт простой контракт для DI: use-case получает httpClient, а внутри может смешивать:
- прямые mutation/one-shot запросы через
httpClient - stateful GET-запросы через
createGet
Использование в компоненте
Актуальный пример из apps/playground/pages/index.vue:
<script lang="ts" setup>
const app = useNuxtApp()
const httpProducts = await app.$di.product.products({
lazy: true,
})
</script>
<template>
<button
type="button"
@click="httpProducts.fetch()"
>
{{ httpProducts.hasFirstData }} Action
</button>
{{ httpProducts.data }}
</template>
Типичный сценарий:
- Создать request-state через
await endpoint({ ...options }) - Если
lazy: true, вызватьfetch()вручную - Читать
data,error,pending,pendingCache
Пример с параметрами:
const productsHttp = await app.$di.product.products({
initParams: {
limit: 10,
},
lazy: true,
})
await productsHttp.fetch({
limit: 30,
})
Cache и синхронизация между вкладками
Если передан getCache, createUseHttp:
- сначала пробует отдать cache
- вызывает
effect(..., { cached: true })для cache-ответа - затем делает сетевой запрос
- сохраняет успешный ответ в cache
- при изменении хеша удаляет связанные cache-ключи через
deleteKeysWithPart
Дополнительно пакет использует BroadcastChannel и синхронизирует свежие успешные GET-ответы между вкладками.
По умолчанию:
ttl = 7 днейchannelName = 'http-tab-sync'
SSR
Если вызвать endpoint с server: true, на сервере пакет использует useLazyAsyncData, сохраняет результат в useState и переиспользует его на клиенте после гидрации.
Это полезно для страниц, где нужен первый ответ уже в SSR, но тот же контракт data / pending / fetch должен остаться и на клиенте.
Вспомогательные функции
import { createURL, hashData } from '@brickflow/http'
createURL(url, params)строит query stringhashData(data)считает SHA-1 черезcrypto.subtle, а без него использует fallback-хеш
Когда что использовать
createHttp:
- POST/мутации
- one-shot запросы
- когда не нужен reactive state
createUseHttp + defineGet:
- SSR-friendly GET
- cache и
pendingCache - повторное использование endpoint-описаний
- ручной
fetch()
createUseCase:
- доменные модули с DI
- единый контракт для доступа к
httpClient
Related Packages
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.0.57 | >=3 | ||
| 0.0.56 | >=3 | ||
| 0.0.55 | >=3 | ||
| 0.0.54 | >=3 | ||
| 0.0.53 | >=3 | ||
| 0.0.52 | >=3 | ||
| 0.0.51 | >=3 | ||
| 0.0.50 | >=3 | ||
| 0.0.49 | >=3 | ||
| 0.0.48 | >=3 | ||
| 0.0.47 | >=3 | ||
| 0.0.46 | >=3 | ||
| 0.0.45 | >=3 | ||
| 0.0.44 | >=3 | ||
| 0.0.43 | >=3 | ||
| 0.0.41 | >=3 | ||
| 0.0.40 | >=3 | ||
| 0.0.39 | >=3 | ||
| 0.0.38 | >=3 | ||
| 0.0.37 | >=3 | ||
| 0.0.36 | >=3 | ||
| 0.0.35 | >=3 | ||
| 0.0.34 | >=3 | ||
| 0.0.33 | >=3 | ||
| 0.0.32 | >=3 | ||
| 0.0.31 | >=3 | ||
| 0.0.30 | >=3 | ||
| 0.0.29 | >=3 | ||
| 0.0.28 | >=3 | ||
| 0.0.27 | >=3 |
Showing the latest 30 of 56. See every release on npm