@brickflow/http 0.0.15 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,425 +1,390 @@
1
1
  # @brickflow/http
2
2
 
3
- Minimal HTTP package used in this repo for:
3
+ Минимальный HTTP-пакет для brickflow:
4
4
 
5
- - low-level HTTP transport via `createHttp`
6
- - Nuxt async state wrapper via `createUseHttp`
7
- - reusable endpoint factory via `createGet`
8
- - project-level typing through `declare module '@brickflow/http'`
5
+ - `createHttp` для низкоуровневых `GET`/`POST`
6
+ - `createUseHttp` для Nuxt state-обёртки с cache, SSR и ручным `fetch`
7
+ - `defineGet` для типизированных endpoint-фабрик
8
+ - `createUseCase` для DI-паттерна в доменных модулях
9
9
 
10
- This package does not know your API schema by default. The schema and error type are configured in the consumer project through `type.d.ts`.
10
+ Актуальные примеры в репозитории лежат в `apps/playground`.
11
11
 
12
12
  ## Exports
13
13
 
14
- From `@brickflow/http`:
14
+ Рекомендуемый импорт:
15
+
16
+ ```ts
17
+ import { createHttp, createUseHttp, createUseCase, defineGet } from '@brickflow/http'
18
+ ```
19
+
20
+ Из пакета также экспортируются:
15
21
 
16
- - `createHttp`
17
22
  - `createURL`
18
23
  - `hashData`
19
- - HTTP types: `HttpClient`, `HttpError`, `HttpKey`, `HttpParam`, `HttpResponse`, `HttpResponseData`, `HttpSuccessData`
24
+ - типы `HttpClient`, `HttpConfig`, `HttpEndpoint`, `HttpKey`, `HttpParam`, `HttpResponse`, `HttpResponseData`
25
+ - Nuxt-типы `UseHttpFn`, `UseHttpOptions`, `UseHttpResult`
20
26
 
21
- From `@brickflow/http/nuxt`:
27
+ Сабпуть `@brickflow/http/nuxt` остаётся доступным, но в текущей кодовой базе `playground` использует импорты из корня.
22
28
 
23
- - `createUseHttp`
24
- - `createGet`
25
- - Nuxt types: `UseHttpOptions`, `UseHttpResult`, `UseHttpFn`
29
+ ## Типизация API
26
30
 
27
- ## 1. Configure API types in `type.d.ts`
31
+ Пакет не знает схему вашего API заранее. Её нужно объявить в проекте через `declare module '@brickflow/http'`.
28
32
 
29
- In this repo the typing is configured in `types/http.d.ts`.
33
+ Актуальный пример из `apps/playground/types/http.d.ts`:
30
34
 
31
35
  ```ts
32
- import type { Convention, ConventionKeys } from 'convention'
33
-
34
- type BrickHttpError = Exclude<Convention<ConventionKeys>, { status: 'success' }>
35
- type BrickHttpSchema = {
36
- [TKey in ConventionKeys]: Convention<TKey>
36
+ import type { HttpKey, HttpResponseData } from '@brickflow/http'
37
+
38
+ type Convention = {
39
+ '/api/http-error-demo': null
40
+ '/products':
41
+ | {
42
+ limit: number
43
+ products: {
44
+ category: string
45
+ id: number
46
+ price: number
47
+ rating: number
48
+ thumbnail: string
49
+ title: string
50
+ }[]
51
+ skip: number
52
+ total: number
53
+ }
54
+ | {
55
+ message: 'error'
56
+ }
37
57
  }
38
58
 
39
59
  declare module '@brickflow/http' {
40
- interface HttpTypeConfig {
41
- endpoints: BrickHttpSchema
42
- error: BrickHttpError
60
+ interface HttpConfig<TKey extends HttpKey = HttpKey> {
61
+ ignore?: (data: HttpResponseData<TKey>) => boolean
43
62
  }
63
+
64
+ interface HttpEndpoint extends Convention {}
44
65
  }
66
+
67
+ export {}
45
68
  ```
46
69
 
47
- What this gives:
70
+ Что это даёт:
48
71
 
49
- - `HttpKey` becomes your endpoint union
50
- - `HttpResponseData<'some/endpoint'>` becomes the response type for that endpoint
51
- - `HttpSuccessData<'some/endpoint'>` becomes response without the configured error branch
52
- - `UseHttpResult<T, P>['error']` is inferred from `HttpTypeConfig['error']`
72
+ - `HttpKey` становится union из endpoint-ключей
73
+ - `httpClient.get('/products')` получает корректный тип ответа
74
+ - `createGet<Params>()('/products')` наследует тот же контракт
75
+ - `HttpConfig` можно расширять своими полями, как в playground через `ignore`
53
76
 
54
- This is the main place where project typing should live.
77
+ Если нужен типизированный `error` в `UseHttpResult`, дополнительно расширьте `HttpErrorMap`. По умолчанию `error` имеет тип `never`, а разделение между `data` и `error` зависит только от runtime `isError`.
55
78
 
56
- ## 2. Create low-level HTTP client
79
+ ## createHttp
57
80
 
58
- Real usage from `plugins/01.di.ts`:
81
+ Актуальный пример из `apps/playground/plugins/01.di.ts`:
59
82
 
60
83
  ```ts
61
84
  import { createHttp } from '@brickflow/http'
62
85
 
86
+ import { handlePlaygroundResponse } from '~/core/http-error'
87
+
63
88
  const httpClient = createHttp({
64
- baseURL: apiUrl,
65
- headers: () => ({
66
- 'Client-Env': config.public.isDev ? 'development' : undefined,
67
- 'Client-Lang': String(locale.value ?? 'en'),
68
- 'Client-Socket-Id': socketClient?.io.id || '',
69
- 'X-Requested-With': 'XMLHttpRequest',
70
- }),
71
- onResponseError: responseInterceptor,
89
+ baseURL: 'https://dummyjson.com',
90
+ headers: {
91
+ 'X-Playground-Http': 'playground-runtime',
92
+ },
93
+ onResponse: handlePlaygroundResponse,
72
94
  })
73
95
  ```
74
96
 
75
- Available options:
97
+ Поддерживаемые опции:
76
98
 
77
- - `baseURL`
78
- - `fetch`
79
- - `headers`
80
- - `onResponseError`
81
- - `requestInit`
82
- - `timeout`
99
+ - `baseURL: string`
100
+ - `arrayMode?: 'json' | 'repeat'`
101
+ - `fetch?: typeof fetch`
102
+ - `headers?: Record<string, string | undefined> | (() => Record<string, string | undefined>)`
103
+ - `onResponse?: (response) => void | Promise<void>`
104
+ - `timeout?: number`
83
105
 
84
- `createHttp` returns:
106
+ ### GET
85
107
 
86
108
  ```ts
87
- interface HttpClient {
88
- get(url, config?)
89
- post(url, data?, config?)
90
- }
109
+ const response = await httpClient.get('/products', {
110
+ params: {
111
+ limit: 10,
112
+ },
113
+ retry: {
114
+ delay: 300,
115
+ retries: 3,
116
+ },
117
+ })
91
118
  ```
92
119
 
93
- ## 3. Use `HttpClient` directly
120
+ Особенности:
94
121
 
95
- Used in domain methods that do not need cached async state.
122
+ - query строится из `params`
123
+ - `GET` поддерживает retry для `429` и `5xx`
124
+ - delay экспоненциальный: `delay * 2 ** attempt`
125
+ - `signal` можно передать как один `AbortSignal` или массив `AbortSignal[]`
96
126
 
97
- Example from `domains/content/use-case.ts`:
127
+ ### POST
98
128
 
99
129
  ```ts
100
- async create(title: string, type: string, description?: string) {
101
- const { data } = await httpClient.post('content/create', {
102
- description,
103
- title,
104
- type,
105
- })
106
-
107
- return data
108
- }
130
+ const { data } = await httpClient.post('/api/http-error-demo')
109
131
  ```
110
132
 
111
- Another example:
133
+ Для `POST`:
134
+
135
+ - `Record<string, unknown>` сериализуется в JSON
136
+ - `FormData` отправляется как есть
137
+ - retry нет
138
+ - если response не JSON, пакет вернёт пустой объект в `data`
139
+
140
+ ### Расширение `HttpConfig`
141
+
142
+ Так как `HttpConfig` расширяемый, можно прокидывать свои поля в запрос и читать их в `onResponse`.
143
+
144
+ Пример из playground:
112
145
 
113
146
  ```ts
114
- async updateType(itemId: string, type: string) {
115
- const { data } = await httpClient.post('content/update-type', {
116
- itemId,
117
- type,
118
- })
147
+ httpClient.get('/products', {
148
+ ignore: (data) => data?.limit === 20,
149
+ })
150
+ ```
119
151
 
120
- return data
152
+ ```ts
153
+ export function handlePlaygroundResponse(
154
+ response: Parameters<NonNullable<CreateHttpOptions['onResponse']>>[0],
155
+ ): void {
156
+ console.log(response.config.ignore?.(response.data))
121
157
  }
122
158
  ```
123
159
 
124
- Use this style when:
160
+ ## createUseHttp
125
161
 
126
- - request is one-shot
127
- - you do not need `pending`, `hasFirstData`, cache, SSR sync, or `fetch()`
162
+ В `apps/playground/core/http.ts` app-level инстанс создаётся один раз:
128
163
 
129
- ## 4. Create Nuxt `useHttp`
164
+ ```ts
165
+ import { createUseHttp, defineGet } from '@brickflow/http'
130
166
 
131
- In this repo the app-level `useHttp` is created once in `core/util.ts`:
167
+ import { dbDeleteKeysWithPart, dbGet, dbSafeSet } from './indexdb'
132
168
 
133
- ```ts
134
- import { createUseHttp } from '@brickflow/http/nuxt'
169
+ const CACHE_DB_NAME = 'smart-cache-v2'
170
+ const CACHE_STORE_NAME = 'playground-http'
135
171
 
136
172
  const useHttp = createUseHttp({
137
173
  getCache: () => ({
138
- deleteKeysWithPart: async (part) => await dbDeleteKeysWithPart(part, 'smart-cache-v2', STORE_NAME),
139
- get: async (key) => await dbGet(key, 'smart-cache-v2', STORE_NAME),
140
- set: async (key, value, ttl) => await dbSafeSet(key, value, 'smart-cache-v2', STORE_NAME, ttl),
174
+ deleteKeysWithPart: async (part: string) => await dbDeleteKeysWithPart(part, CACHE_DB_NAME, CACHE_STORE_NAME),
175
+ get: async <T>(key: string) => await dbGet<T>(key, CACHE_DB_NAME, CACHE_STORE_NAME),
176
+ set: async <T>(key: string, value: T, ttl: number) =>
177
+ await dbSafeSet<T>(key, value, CACHE_DB_NAME, CACHE_STORE_NAME, ttl),
141
178
  }),
142
179
  getHttpClient: () => useNuxtApp().$http,
143
- isDev: () => useRuntimeConfig().public.isDev,
180
+ isDev: () => false,
144
181
  })
145
- ```
146
-
147
- Dependencies:
148
-
149
- - `getHttpClient`
150
- - `getCache`
151
- - `isError`
152
- - `isDev`
153
- - `ttl`
154
- - `channelName`
155
182
 
156
- `isError` can be set globally here if your project needs a custom runtime error predicate.
183
+ export const createGet = defineGet(useHttp)
184
+ ```
157
185
 
158
- ## 5. Bind `createGet` once
186
+ Зависимости:
159
187
 
160
- The package exports `createGet(useHttp)`, which builds typed endpoint helpers on top of your local `useHttp`.
188
+ - `getHttpClient` обязательно
189
+ - `getCache` опционально
190
+ - `isDev` опционально
191
+ - `isError` опционально
192
+ - `ttl` опционально
193
+ - `channelName` опционально
161
194
 
162
- Real usage from `core/util.ts`:
195
+ `createUseHttp` возвращает функцию `useHttp(options)`, которая создаёт реактивное состояние запроса:
163
196
 
164
- ```ts
165
- import { createGet as createHttpGet, createUseHttp } from '@brickflow/http/nuxt'
197
+ - `data`
198
+ - `error`
199
+ - `pending`
200
+ - `pendingCache`
201
+ - `hasFirstData`
202
+ - `hasFreshData`
203
+ - `fetch(params?, { signal? })`
166
204
 
167
- const useHttp = createUseHttp({ ... })
205
+ ### Опции useHttp
168
206
 
169
- export const createGet = createHttpGet(useHttp)
170
- ```
207
+ - `url`
208
+ - `initParams`
209
+ - `mapParams`
210
+ - `effect`
211
+ - `isError`
212
+ - `lazy`
213
+ - `server`
171
214
 
172
- After that you can reuse `createGet` in domain files.
215
+ `effect(data, config)` вызывается и для cache, и для fresh-response:
173
216
 
174
- ## 6. Create typed GET endpoints
217
+ - `config.cached === true` для cache
218
+ - `config.cached === false` для сети
219
+ - `config.params` содержит уже применённые params
175
220
 
176
- ### Without params
221
+ ## defineGet
177
222
 
178
- Example from `domains/storage/use-case.ts`:
223
+ `defineGet` связывает app-level `useHttp` с endpoint-фабриками:
179
224
 
180
225
  ```ts
181
- plans: createGet()('storage/plans')
182
- ```
183
-
184
- ### With params
185
-
186
- Example from `domains/feed/use-case.ts`:
226
+ const useHttp = createUseHttp({ ... })
187
227
 
188
- ```ts
189
- home: createGet<{
190
- category?: 'fresh' | 'popular' | 'recommended' | 'updated'
191
- limit: number
192
- offset: number
193
- }>()('feed/home')
228
+ export const createGet = defineGet(useHttp)
194
229
  ```
195
230
 
196
- ### With `ignore`
197
-
198
- Example from `domains/content/use-case.ts`:
231
+ Дальше в домене можно описывать запросы коротко и типизированно:
199
232
 
200
233
  ```ts
201
- analytics: createGet<{
202
- itemId: string
203
- period: '7d' | '28d' | '90d'
204
- }>()('content/analytics', {
205
- ignore(res) {
206
- if (res.data.kind === 'no_auth') {
207
- return true
208
- }
209
-
210
- return false
211
- },
212
- })
234
+ products: createGet<{
235
+ limit: number
236
+ }>()('/products')
213
237
  ```
214
238
 
215
- ### With `effect`
239
+ `defineGet` поддерживает два слоя настроек:
216
240
 
217
- Example from `domains/root/use-case.ts`:
241
+ 1. Базовые настройки endpoint-а при объявлении:
218
242
 
219
243
  ```ts
220
- main: createGet()('root/main', {
221
- effect(data, config) {
222
- if (data.status === 'error') {
223
- return
224
- }
225
-
226
- if (!config.cached) {
227
- userStore.setUser(data.user)
228
- userStore.setRegion(data.region)
229
- }
230
- },
244
+ const products = createGet<{ limit: number }>()('/products', {
245
+ mapParams: (params) => ({
246
+ limit: params?.limit ?? 10,
247
+ }),
231
248
  })
232
249
  ```
233
250
 
234
- ### With `effect` and success handling
235
-
236
- Example from `domains/storage/use-case.ts`:
251
+ 2. Runtime-опции при вызове:
237
252
 
238
253
  ```ts
239
- current: createGet()('storage/current', {
240
- effect(data) {
241
- if (data.status === 'success') {
242
- storageStore.setCurrentPlan(data.plan)
243
- }
244
- },
254
+ const productsHttp = await products({
255
+ initParams: { limit: 20 },
256
+ lazy: true,
257
+ server: false,
245
258
  })
246
259
  ```
247
260
 
248
- ## 7. Consume endpoint state in components
261
+ Если `effect`, `mapParams` или `isError` указаны и при объявлении, и при вызове, пакет объединяет их так:
249
262
 
250
- ### Create request object
263
+ - `effect` вызывает оба обработчика
264
+ - `isError` из runtime имеет приоритет
265
+ - `mapParams` из runtime имеет приоритет
251
266
 
252
- Example from `components/Search/index.vue`:
267
+ ## createUseCase
253
268
 
254
- ```ts
255
- const searchFastHttp = await app.$di.search.fast({
256
- lazy: true,
257
- server: false,
258
- })
259
- ```
260
-
261
- ### Trigger fetch manually
269
+ В playground доменный модуль собирается через `createUseCase`:
262
270
 
263
271
  ```ts
264
- await searchFastHttp.fetch({
265
- text: searchQuery.value,
266
- })
267
- ```
272
+ import { createUseCase } from '@brickflow/http'
268
273
 
269
- ### Read `data`
274
+ import { createGet } from '~/core/http'
270
275
 
271
- ```ts
272
- const data = searchFastHttp.data
276
+ export default createUseCase()(({ httpClient }) => {
277
+ return {
278
+ async apiError() {
279
+ const { data } = await httpClient.post('/api/http-error-demo')
280
+ return data
281
+ },
273
282
 
274
- if (data?.status === 'success') {
275
- items.value = data.items
276
- users.value = data.users
277
- }
283
+ products: createGet<{
284
+ limit: number
285
+ }>()('/products'),
286
+ }
287
+ })
278
288
  ```
279
289
 
280
- ### Read loading flags
290
+ Это даёт простой контракт для DI: use-case получает `httpClient`, а внутри может смешивать:
281
291
 
282
- Example from `components/ProfileMyGrid/index.vue`:
292
+ - прямые mutation/one-shot запросы через `httpClient`
293
+ - stateful GET-запросы через `createGet`
283
294
 
284
- ```vue
285
- :loading="myContentHttp.pending || myContentHttp.pendingCache"
286
- ```
295
+ ## Использование в компоненте
287
296
 
288
- ### Use `hasFirstData`
297
+ Актуальный пример из `apps/playground/pages/index.vue`:
289
298
 
290
299
  ```vue
291
- v-if="!myContentHttp.pending && myContentHttp.hasFirstData && items.length === 0"
300
+ <script lang="ts" setup>
301
+ const app = useNuxtApp()
302
+ const httpProducts = await app.$di.product.products({
303
+ lazy: true,
304
+ })
305
+ </script>
306
+
307
+ <template>
308
+ <button
309
+ type="button"
310
+ @click="httpProducts.fetch()"
311
+ >
312
+ {{ httpProducts.hasFirstData }} Action
313
+ </button>
314
+
315
+ {{ httpProducts.data }}
316
+ </template>
292
317
  ```
293
318
 
294
- ## 8. `effect` semantics
319
+ Типичный сценарий:
295
320
 
296
- `effect` receives the full response payload.
321
+ 1. Создать request-state через `await endpoint({ ...options })`
322
+ 2. Если `lazy: true`, вызвать `fetch()` вручную
323
+ 3. Читать `data`, `error`, `pending`, `pendingCache`
297
324
 
298
- That means:
299
-
300
- - it is called for success
301
- - it is called for error
302
- - you can branch on `data.status`
303
- - `config.cached` tells whether the payload came from cache
304
- - `config.params` contains mapped params used for the request
305
-
306
- Example from `pages/profile/[username].vue`:
325
+ Пример с параметрами:
307
326
 
308
327
  ```ts
309
- const profileHttp = await app.$di.profile.info({
310
- effect(data) {
311
- if (data.status === 'error' && data.kind === 'not_found') {
312
- showError(createError({
313
- fatal: true,
314
- statusCode: 404,
315
- statusMessage: 'Page Not Found',
316
- }))
317
- }
318
- },
328
+ const productsHttp = await app.$di.product.products({
319
329
  initParams: {
320
- username: String(profileParam.value),
330
+ limit: 10,
321
331
  },
322
- server: true,
332
+ lazy: true,
323
333
  })
324
- ```
325
-
326
- ## 9. `data` and `error`
327
334
 
328
- `UseHttpResult<T, P>` is split like this:
329
-
330
- - `data`: success payload only
331
- - `error`: configured project error payload only
332
-
333
- Internally the split is done by runtime error predicate:
334
-
335
- - global `isError` from `createUseHttp(...)`
336
- - or per-request `isError`
337
- - fallback: `payload.status === 'error'`
338
-
339
- Important:
340
-
341
- - runtime error predicate does not reconfigure TypeScript types
342
- - TypeScript error type comes from `declare module '@brickflow/http'`
343
-
344
- ## 10. Supported request options
345
-
346
- For `useHttp` / `createGet`:
347
-
348
- - `url`
349
- - `initParams`
350
- - `mapParams`
351
- - `effect`
352
- - `ignore`
353
- - `isError`
354
- - `lazy`
355
- - `server`
356
-
357
- Example with `initParams`:
358
-
359
- ```ts
360
- const myContentHttp = await app.$di.content.my({
361
- initParams: {
362
- limit: props.limit,
363
- offset: 0,
364
- order: selectedOrder.value,
365
- orderColumn: 'date',
366
- type: selectedType.value,
367
- },
335
+ await productsHttp.fetch({
336
+ limit: 30,
368
337
  })
369
338
  ```
370
339
 
371
- Example with `fetch(...)` overriding params:
340
+ ## Cache и синхронизация между вкладками
372
341
 
373
- ```ts
374
- await myContentHttp.fetch({
375
- limit: props.limit,
376
- offset,
377
- order: selectedOrder.value,
378
- orderColumn: 'date',
379
- type: selectedType.value,
380
- })
381
- ```
342
+ Если передан `getCache`, `createUseHttp`:
382
343
 
383
- ## 11. Cache behavior
344
+ - сначала пробует отдать cache
345
+ - вызывает `effect(..., { cached: true })` для cache-ответа
346
+ - затем делает сетевой запрос
347
+ - сохраняет успешный ответ в cache
348
+ - при изменении хеша удаляет связанные cache-ключи через `deleteKeysWithPart`
384
349
 
385
- When `getCache` is provided, `createUseHttp`:
350
+ Дополнительно пакет использует `BroadcastChannel` и синхронизирует свежие успешные GET-ответы между вкладками.
386
351
 
387
- - tries cached value first
388
- - calls `effect(..., { cached: true })` for cached payload
389
- - then performs fresh request
390
- - stores success response in cache
391
- - invalidates related keys when response hash changes
352
+ По умолчанию:
392
353
 
393
- In this repo the cache is backed by IndexedDB in `core/util.ts`.
354
+ - `ttl = 7 дней`
355
+ - `channelName = 'http-tab-sync'`
394
356
 
395
- ## 12. URL and hash helpers
357
+ ## SSR
396
358
 
397
- The package also exports helpers used by the current implementation:
359
+ Если вызвать endpoint с `server: true`, на сервере пакет использует `useLazyAsyncData`, сохраняет результат в `useState` и переиспользует его на клиенте после гидрации.
360
+
361
+ Это полезно для страниц, где нужен первый ответ уже в SSR, но тот же контракт `data / pending / fetch` должен остаться и на клиенте.
362
+
363
+ ## Вспомогательные функции
398
364
 
399
365
  ```ts
400
366
  import { createURL, hashData } from '@brickflow/http'
401
367
  ```
402
368
 
403
- `createURL(url, params)` builds query strings.
404
-
405
- `hashData(data)` computes response hash for cache invalidation.
369
+ - `createURL(url, params)` строит query string
370
+ - `hashData(data)` считает SHA-1 через `crypto.subtle`, а без него использует fallback-хеш
406
371
 
407
- ## 13. Recommended usage in this repo
372
+ ## Когда что использовать
408
373
 
409
- Use `createHttp` when:
374
+ `createHttp`:
410
375
 
411
- - you need direct `get/post`
412
- - you are writing mutations or one-shot calls
376
+ - POST/мутации
377
+ - one-shot запросы
378
+ - когда не нужен reactive state
413
379
 
414
- Use `createUseHttp` + `createGet` when:
380
+ `createUseHttp` + `defineGet`:
415
381
 
416
- - you need SSR-aware async state
417
- - you need `pending`, `pendingCache`, `hasFirstData`, `hasFreshData`
418
- - you need cache integration
419
- - you want reusable typed endpoint factories in domain modules
382
+ - SSR-friendly GET
383
+ - cache и `pendingCache`
384
+ - повторное использование endpoint-описаний
385
+ - ручной `fetch()`
420
386
 
421
- Use `type.d.ts` when:
387
+ `createUseCase`:
422
388
 
423
- - you want to define endpoint keys
424
- - you want to define the project-wide error type
425
- - you want all consumers to infer the same API schema
389
+ - доменные модули с DI
390
+ - единый контракт для доступа к `httpClient`