@ryuzaki13/react-foundation-api 1.1.16 → 1.1.17
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 +32 -43
- package/dist/chunks/{odataFetchFn-vnAXC-c0.js → odataFetchFn-B9wSQpUS.js} +11 -11
- package/dist/chunks/{odataFetchFn-vnAXC-c0.js.map → odataFetchFn-B9wSQpUS.js.map} +1 -1
- package/dist/odata/fetchCollectionData.d.ts +1 -1
- package/dist/odata/fetchCollectionData.d.ts.map +1 -1
- package/dist/odata/index.js +111 -112
- package/dist/odata/index.js.map +1 -1
- package/dist/odata/projectODataCollectionSort.d.ts +1 -1
- package/dist/odata/projectODataCollectionSort.d.ts.map +1 -1
- package/dist/odata/types.d.ts +1 -2
- package/dist/odata/types.d.ts.map +1 -1
- package/dist/odata/useODataCollection.d.ts +1 -1
- package/dist/odata/useODataCollection.d.ts.map +1 -1
- package/dist/odata/useODataCollectionQuery.d.ts +1 -1
- package/dist/odata/useODataCollectionQuery.d.ts.map +1 -1
- package/dist/odata/useODataEntity.d.ts +1 -1
- package/dist/odata/useODataEntity.d.ts.map +1 -1
- package/dist/persisted/index.js +1 -1
- package/package.json +2 -2
- package/src/adt/README.mdx +164 -0
- package/src/async/README.mdx +253 -0
- package/src/error-report/README.mdx +148 -0
- package/src/foundationApi.mdx +123 -0
- package/src/http/README.mdx +221 -0
- package/src/odata/README.mdx +790 -0
- package/src/persisted/README.mdx +454 -0
- package/src/resource/README.mdx +358 -0
- package/src/server-fn/README.mdx +194 -0
- package/src/transport/README.mdx +183 -0
- package/src/README.md +0 -937
- package/src/async/README.md +0 -623
- package/src/async/async.mdx +0 -6
- package/src/odata/README.md +0 -761
- package/src/odata/odataFetchFn.mdx +0 -6
- package/src/persisted/README.md +0 -598
- package/src/persisted/persisted.mdx +0 -6
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Meta } from "@storybook/addon-docs/blocks";
|
|
2
|
+
|
|
3
|
+
<Meta title="Foundation API/Обзор" />
|
|
4
|
+
|
|
5
|
+
# `@ryuzaki13/react-foundation-api`
|
|
6
|
+
|
|
7
|
+
Пакет соединяет приложение с внешними API. Он содержит transport, интеграцию с TanStack Query, SAP/OData V2 infrastructure и небольшие orchestration helpers. Документ рассчитан на разработчика, который видит только опубликованный пакет и его типы.
|
|
8
|
+
|
|
9
|
+
## Подключение
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @ryuzaki13/react-foundation-api @ryuzaki13/react-foundation-lib
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Корневого импорта нет. Всегда указывайте точный subpath:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { httpFetchPayload } from "@ryuzaki13/react-foundation-api/http";
|
|
19
|
+
import { useResourceQuery } from "@ryuzaki13/react-foundation-api/resource";
|
|
20
|
+
import { useODataMetadataQuery } from "@ryuzaki13/react-foundation-api/odata";
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Где находится пакет в архитектуре
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
host application: entities / features / widgets / pages
|
|
27
|
+
│
|
|
28
|
+
▼
|
|
29
|
+
@ryuzaki13/react-foundation-api
|
|
30
|
+
transport + query orchestration
|
|
31
|
+
│
|
|
32
|
+
▼
|
|
33
|
+
@ryuzaki13/react-foundation-lib
|
|
34
|
+
pure helpers + metadata + query policies
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`foundation-api` не должен знать конкретную бизнес-сущность, экран или UI-компонент. DTO/domain mapping конкретного проекта остаётся у host-приложения. UI primitives находятся в `@ryuzaki13/react-foundation-ui`.
|
|
38
|
+
|
|
39
|
+
## Как выбрать entrypoint
|
|
40
|
+
|
|
41
|
+
| Нужно сделать | Entrypoint | Важная граница |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| Вызвать обычный JSON REST endpoint | `http` | Нет SAP/SSO/X-CSRF политики |
|
|
44
|
+
| Вызвать SAP Gateway/OData V2 | `odata` | Metadata, envelope, SSO и X-CSRF принадлежат этому слою |
|
|
45
|
+
| Описать произвольные queries/mutations | `resource` | Transport передаётся операциями |
|
|
46
|
+
| Описать сохранённые records с фиксированными capability | `persisted` | Только list/latest/history/save/create/delete |
|
|
47
|
+
| Подключить функцию вида `fn({ data })` | `server-fn` | Это adapter операции, не сервер и не security boundary |
|
|
48
|
+
| Выполнить пакет независимых задач | `async` | Выберите all-at-once или bounded concurrency |
|
|
49
|
+
| Прочитать ADT transport XML | `adt` | Endpoint `/sap/bc/adt`, XML |
|
|
50
|
+
| Прочитать UI2 workbench/customizing requests | `transport` | Это SAP transport requests, а не generic HTTP transport |
|
|
51
|
+
| Отправить error-report draft | `error-report` | Сбор/хранение draft принадлежит `foundation-lib` |
|
|
52
|
+
|
|
53
|
+
## Самая частая ошибка: одинаковые слова, разные уровни
|
|
54
|
+
|
|
55
|
+
### `http` и `odata`
|
|
56
|
+
|
|
57
|
+
`http` подходит для нейтрального REST. Он не получает CSRF token, не распознаёт SAML form и не снимает OData V2 envelope. Для SAP Gateway используйте `odata`, даже если технически запрос тоже выполняется через `fetch`.
|
|
58
|
+
|
|
59
|
+
### `resource` и `persisted`
|
|
60
|
+
|
|
61
|
+
`resource` позволяет задавать любые имена операций: например `search`, `details`, `archive`. `persisted` — специализированный facade для сохранённых записей с именами `list`, `latest`, `history`, `save`, `create`, `delete`.
|
|
62
|
+
|
|
63
|
+
### `adt` и `transport`
|
|
64
|
+
|
|
65
|
+
Оба entrypoint читают SAP transports, но из разных API:
|
|
66
|
+
|
|
67
|
+
| Entrypoint | Endpoint/format | Результат |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| `adt` | `/sap/bc/adt/cts/transports`, XML | Общий `UserTransport` из ADT header |
|
|
70
|
+
| `transport` | UI2 OData endpoints, JSON | Нормализованные workbench/customizing requests |
|
|
71
|
+
|
|
72
|
+
## TanStack Query: кто чем владеет
|
|
73
|
+
|
|
74
|
+
`resource`, `persisted` и часть `odata` работают внутри `QueryClientProvider`. Query key определяет identity кеша; `staleTime` — свежесть; `gcTime` — время хранения неиспользуемого query; `AbortSignal` позволяет отменять чтение. Mutation не обновляет все связанные query автоматически: это делает явно выбранная cache strategy.
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { QueryClientProvider } from "@tanstack/react-query";
|
|
78
|
+
import { createQueryClient } from "@ryuzaki13/react-foundation-lib/query-client";
|
|
79
|
+
|
|
80
|
+
const queryClient = createQueryClient();
|
|
81
|
+
|
|
82
|
+
export function App() {
|
|
83
|
+
return <QueryClientProvider client={queryClient}>{/* приложение */}</QueryClientProvider>;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Не создавайте новый `QueryClient` при каждом render.
|
|
88
|
+
|
|
89
|
+
## Runtime
|
|
90
|
+
|
|
91
|
+
| API | Browser | Server runtime |
|
|
92
|
+
| --- | :---: | :---: |
|
|
93
|
+
| `async` и pure normalization | Да | Да |
|
|
94
|
+
| `http` | Да | Да, если доступны Web Fetch API |
|
|
95
|
+
| pure `resource` factories | Да | Да |
|
|
96
|
+
| React hooks | Да | Только в поддерживаемом React runtime/provider |
|
|
97
|
+
| OData SSO recovery | Да | Нет: использует `window`, `document`, iframe |
|
|
98
|
+
| SAP transport fetchers | Да | Только при корректном fetch/proxy/cookie окружении |
|
|
99
|
+
|
|
100
|
+
OData transport использует compile-time constants и проектный adapter. Host-сборка должна предоставлять ожидаемую конфигурацию; подробности приведены на странице OData.
|
|
101
|
+
|
|
102
|
+
## Service Worker cache
|
|
103
|
+
|
|
104
|
+
Некоторые query factories передают строку политики через header `x-sw-cache`. Сам header ничего не кеширует: Service Worker host-приложения должен понимать тот же protocol.
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
off
|
|
108
|
+
ttl=24h
|
|
109
|
+
ttl=10m;max=200;name=ui
|
|
110
|
+
bust=24h;name=ref
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`bust` означает принудительное обновление сетевого snapshot в соответствующей cache policy. Не используйте SW cache для персональных/секретных payload без отдельной модели изоляции.
|
|
114
|
+
|
|
115
|
+
## Каталог страниц
|
|
116
|
+
|
|
117
|
+
| Область | Документы |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| Transport | [HTTP](./http/README.mdx), [OData](./odata/README.mdx), [ADT](./adt/README.mdx), [SAP Transport Requests](./transport/README.mdx) |
|
|
120
|
+
| Query infrastructure | [Resource](./resource/README.mdx), [Persisted](./persisted/README.mdx), [Server Function](./server-fn/README.mdx) |
|
|
121
|
+
| Orchestration и diagnostics | [Async](./async/README.mdx), [Error Report](./error-report/README.mdx) |
|
|
122
|
+
|
|
123
|
+
Каждая страница содержит точный import, пошаговый guide, runtime/lifecycle, ошибки и таблицу публичных exports. Не импортируйте файл глубоким путём, если его символ не опубликован соответствующим entrypoint.
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { Meta } from "@storybook/addon-docs/blocks";
|
|
2
|
+
|
|
3
|
+
<Meta title="Foundation API/Transport/HTTP" />
|
|
4
|
+
|
|
5
|
+
# Обычный HTTP через `@ryuzaki13/react-foundation-api/http`
|
|
6
|
+
|
|
7
|
+
Модуль выполняет нейтральные HTTP/REST запросы и создаёт функции, совместимые с TanStack Query. Внешний payload остаётся `unknown`, пока consumer явно не проверит его через `parse`.
|
|
8
|
+
|
|
9
|
+
## Содержание
|
|
10
|
+
|
|
11
|
+
- [Когда использовать](#когда-использовать)
|
|
12
|
+
- [Импорт](#импорт)
|
|
13
|
+
- [`httpFetch`](#httpfetch)
|
|
14
|
+
- [`httpFetchPayload`](#httpfetchpayload)
|
|
15
|
+
- [`httpJsonQueryFn`](#httpjsonqueryfn)
|
|
16
|
+
- [`httpJsonMutationFn`](#httpjsonmutationfn)
|
|
17
|
+
- [`RouteError`](#routeerror)
|
|
18
|
+
- [Ошибки, runtime и безопасность](#ошибки-runtime-и-безопасность)
|
|
19
|
+
- [Полный API](#полный-api)
|
|
20
|
+
|
|
21
|
+
## Когда использовать
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
обычный REST/HTTP ──► /http
|
|
25
|
+
SAP Gateway/OData ──► /odata
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`http` не добавляет SAP headers, `credentials: include`, X-CSRF token, SSO recovery, OData V2 headers и не снимает envelope `{ d: { results } }`. Для SAP/OData используйте [`/odata`](../odata/README.mdx).
|
|
29
|
+
|
|
30
|
+
## Импорт
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import {
|
|
34
|
+
httpFetch,
|
|
35
|
+
httpFetchPayload,
|
|
36
|
+
httpJsonMutationFn,
|
|
37
|
+
httpJsonQueryFn,
|
|
38
|
+
RouteError
|
|
39
|
+
} from "@ryuzaki13/react-foundation-api/http";
|
|
40
|
+
|
|
41
|
+
import type {
|
|
42
|
+
HttpMutationFnOptions,
|
|
43
|
+
HttpQueryFnOptions,
|
|
44
|
+
HttpRequestOptions
|
|
45
|
+
} from "@ryuzaki13/react-foundation-api/http";
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Нужны глобальные Web Fetch API: `fetch`, `Request`, `Response`, `Headers`, `URL`.
|
|
49
|
+
|
|
50
|
+
## `httpFetch`
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
const response = await httpFetch("/health", {
|
|
54
|
+
baseUrl: "https://api.example.com",
|
|
55
|
+
init: {
|
|
56
|
+
method: "GET",
|
|
57
|
+
headers: { Accept: "text/plain" }
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
console.log(await response.text());
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Функция возвращает исходный `Response`, если `response.ok === true`. Body ещё не прочитан.
|
|
65
|
+
|
|
66
|
+
Для string input URL строится простой конкатенацией:
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
baseUrl + input
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Helper не исправляет slash:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
// Правильно.
|
|
76
|
+
{ baseUrl: "https://api.example.com", input: "/users" }
|
|
77
|
+
|
|
78
|
+
// Получится https://api.example.comusers — неверно.
|
|
79
|
+
{ baseUrl: "https://api.example.com", input: "users" }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Если input является `Request` или `URL`, `baseUrl` игнорируется. Это не proxy/url resolver.
|
|
83
|
+
|
|
84
|
+
## `httpFetchPayload`
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const payload: unknown = await httpFetchPayload("/api/profile");
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Правила parser-а:
|
|
91
|
+
|
|
92
|
+
| Response | Результат |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| status `204` или `205` | `null` |
|
|
95
|
+
| `Content-Type` содержит `application/json` | `response.json()` |
|
|
96
|
+
| любой другой content type | `response.text()` |
|
|
97
|
+
|
|
98
|
+
Результат намеренно `unknown`. Generic type не может проверить данные, пришедшие по сети:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
function parseProfile(value: unknown): Profile {
|
|
102
|
+
if (!isProfile(value)) throw new Error("Некорректный Profile payload");
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const profile = parseProfile(await httpFetchPayload("/api/profile"));
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Если сервер отдаёт JSON без корректного `Content-Type`, модуль вернёт JSON как string. Исправьте backend или разберите string на своей boundary.
|
|
110
|
+
|
|
111
|
+
## `httpJsonQueryFn`
|
|
112
|
+
|
|
113
|
+
Factory возвращает async-функцию с формой TanStack Query `queryFn`:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const loadProfile = httpJsonQueryFn("/api/profile", {
|
|
117
|
+
baseUrl: "https://api.example.com",
|
|
118
|
+
parse: parseProfile,
|
|
119
|
+
swCache: "ttl=10m;name=profile"
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const profile = await loadProfile({ signal });
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
С React Query:
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
const profileQuery = useQuery({
|
|
129
|
+
queryKey: ["profile", userId],
|
|
130
|
+
queryFn: httpJsonQueryFn(`/api/users/${userId}`, {
|
|
131
|
+
parse: parseProfile
|
|
132
|
+
})
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Factory:
|
|
137
|
+
|
|
138
|
+
- использует `init.signal`, если он задан явно;
|
|
139
|
+
- иначе подставляет signal, переданный TanStack Query;
|
|
140
|
+
- добавляет `x-sw-cache`, только если `swCache` truthy;
|
|
141
|
+
- вызывает обязательный `parse(payload)` после успешного запроса.
|
|
142
|
+
|
|
143
|
+
Header `x-sw-cache` — только protocol с Service Worker. Без соответствующего SW он не создаёт cache.
|
|
144
|
+
|
|
145
|
+
`parse` может вернуть преобразованную domain model, но обычно лучше ограничиться runtime validation и вынести бизнес-mapping владельцу entity.
|
|
146
|
+
|
|
147
|
+
## `httpJsonMutationFn`
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
type CreateUserInput = { name: string };
|
|
151
|
+
|
|
152
|
+
const createUser = httpJsonMutationFn<CreateUserInput, User>("/api/users", {
|
|
153
|
+
method: "POST",
|
|
154
|
+
mapBody: (input) => ({ displayName: input.name }),
|
|
155
|
+
parse: parseUser
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const user = await createUser({ name: "Анна" });
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
С `useMutation`:
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
const mutation = useMutation({ mutationFn: createUser });
|
|
165
|
+
mutation.mutate({ name: "Анна" });
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Поведение:
|
|
169
|
+
|
|
170
|
+
1. Вычисляет body через `mapBody(input)` или использует input.
|
|
171
|
+
2. Сериализует body через `JSON.stringify`.
|
|
172
|
+
3. Использует method `POST` по умолчанию; поддерживаются `POST`, `PUT`, `PATCH`, `DELETE`.
|
|
173
|
+
4. Добавляет `Content-Type: application/json`, только если header ещё не задан.
|
|
174
|
+
5. Выполняет запрос и передаёт payload в обязательный `parse`.
|
|
175
|
+
|
|
176
|
+
Сигнатура mutation function также допускает второй `AbortSignal` при ручном вызове. Стандартный `useMutation` TanStack Query не передаёт signal автоматически. Если `options.init.signal` уже задан, второй signal не заменит его.
|
|
177
|
+
|
|
178
|
+
`JSON.stringify` может бросить ошибку для `BigInt`/циклического объекта. `undefined` body сериализуется в `undefined`; убедитесь, что это соответствует endpoint.
|
|
179
|
+
|
|
180
|
+
## HTTP errors
|
|
181
|
+
|
|
182
|
+
Для `response.ok === false` обе низкоуровневые функции бросают обычный `Error`:
|
|
183
|
+
|
|
184
|
+
```text
|
|
185
|
+
HTTP error: <message>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Если error body — непустой text, он становится message. Для JSON error object поля `message`, `error` и `details` не извлекаются; fallback — `<status> <statusText>`.
|
|
189
|
+
|
|
190
|
+
Network error/CORS/abort от глобального `fetch` пробрасывается без обёртки.
|
|
191
|
+
|
|
192
|
+
## `RouteError`
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
throw new RouteError(404);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Это маленький `Error` с числовым полем `status` и пустым message. `httpFetch` сам его не создаёт. Класс нужен для routing/error-boundary policy приложения, если оно договорилось понимать такой error.
|
|
199
|
+
|
|
200
|
+
## Ошибки, runtime и безопасность
|
|
201
|
+
|
|
202
|
+
- TypeScript generic не валидирует сеть; используйте `parse`/schema guard.
|
|
203
|
+
- `baseUrl` и path не нормализуются и не кодируются.
|
|
204
|
+
- Authorization, cookies, CORS и retry задаёт consumer через `init` или внешний слой.
|
|
205
|
+
- Модуль не делает timeout. Для него используйте `AbortController`.
|
|
206
|
+
- Не помещайте token/secret в query string и cache key.
|
|
207
|
+
- `x-sw-cache` для персональных данных требует изолированной cache policy.
|
|
208
|
+
- На SSR абсолютный/относительный URL и cookies должны быть настроены server runtime-ом.
|
|
209
|
+
|
|
210
|
+
## Полный API
|
|
211
|
+
|
|
212
|
+
| Export | Результат |
|
|
213
|
+
| --- | --- |
|
|
214
|
+
| `httpFetch` | Успешный `Response` |
|
|
215
|
+
| `httpFetchPayload` | JSON/text/`null` как `unknown` |
|
|
216
|
+
| `httpJsonQueryFn` | Query-compatible async function |
|
|
217
|
+
| `httpJsonMutationFn` | Mutation-compatible async function |
|
|
218
|
+
| `RouteError` | Error с `status` |
|
|
219
|
+
| `HttpRequestOptions` | `baseUrl` и `init` |
|
|
220
|
+
| `HttpQueryFnOptions` | Request options + `swCache` + `parse` |
|
|
221
|
+
| `HttpMutationFnOptions` | Request options + method/body mapper/parse |
|