@ryuzaki13/react-foundation-api 1.1.16 → 1.1.18

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.
Files changed (36) hide show
  1. package/README.md +32 -43
  2. package/dist/chunks/{odataFetchFn-vnAXC-c0.js → odataFetchFn-B9wSQpUS.js} +11 -11
  3. package/dist/chunks/{odataFetchFn-vnAXC-c0.js.map → odataFetchFn-B9wSQpUS.js.map} +1 -1
  4. package/dist/odata/fetchCollectionData.d.ts +1 -1
  5. package/dist/odata/fetchCollectionData.d.ts.map +1 -1
  6. package/dist/odata/index.js +111 -112
  7. package/dist/odata/index.js.map +1 -1
  8. package/dist/odata/projectODataCollectionSort.d.ts +1 -1
  9. package/dist/odata/projectODataCollectionSort.d.ts.map +1 -1
  10. package/dist/odata/types.d.ts +1 -2
  11. package/dist/odata/types.d.ts.map +1 -1
  12. package/dist/odata/useODataCollection.d.ts +1 -1
  13. package/dist/odata/useODataCollection.d.ts.map +1 -1
  14. package/dist/odata/useODataCollectionQuery.d.ts +1 -1
  15. package/dist/odata/useODataCollectionQuery.d.ts.map +1 -1
  16. package/dist/odata/useODataEntity.d.ts +1 -1
  17. package/dist/odata/useODataEntity.d.ts.map +1 -1
  18. package/dist/persisted/index.js +1 -1
  19. package/package.json +2 -2
  20. package/src/adt/README.mdx +461 -0
  21. package/src/async/README.mdx +628 -0
  22. package/src/error-report/README.mdx +471 -0
  23. package/src/foundationApi.mdx +123 -0
  24. package/src/http/README.mdx +570 -0
  25. package/src/odata/README.mdx +5142 -0
  26. package/src/persisted/README.mdx +1080 -0
  27. package/src/resource/README.mdx +820 -0
  28. package/src/server-fn/README.mdx +596 -0
  29. package/src/transport/README.mdx +528 -0
  30. package/src/README.md +0 -937
  31. package/src/async/README.md +0 -623
  32. package/src/async/async.mdx +0 -6
  33. package/src/odata/README.md +0 -761
  34. package/src/odata/odataFetchFn.mdx +0 -6
  35. package/src/persisted/README.md +0 -598
  36. package/src/persisted/persisted.mdx +0 -6
@@ -0,0 +1,461 @@
1
+ import { Meta } from "@storybook/addon-docs/blocks";
2
+
3
+ <Meta title="Foundation API/SAP/ADT Transports" />
4
+
5
+ # SAP ADT transports через `@ryuzaki13/react-foundation-api/adt`
6
+
7
+ Модуль получает transport requests текущего пользователя из SAP ADT endpoint и преобразует XML в простой массив объектов.
8
+
9
+ ## Содержание
10
+
11
+ - [Когда использовать](#когда-использовать)
12
+ - [Установка и импорт](#установка-и-импорт)
13
+ - [Быстрый старт](#быстрый-старт)
14
+ - [`fetchUserTransports`](#fetchusertransports)
15
+ - [`parseUserTransportsXml`](#parseusertransportsxml)
16
+ - [Формат результата](#формат-результата)
17
+ - [Ошибки и ограничения](#ошибки-и-ограничения)
18
+ - [Полный API](#полный-api)
19
+
20
+ ## Когда использовать
21
+
22
+ Используйте `adt`, если backend доступен по `/sap/bc/adt/cts/transports` и возвращает ADT XML. Если нужны отдельные UI2 списки workbench/customizing в JSON, используйте [`@ryuzaki13/react-foundation-api/transport`](../transport/README.mdx).
23
+
24
+ ```text
25
+ SAP ADT XML
26
+
27
+
28
+ parseUserTransportsXml
29
+
30
+
31
+ UserTransport[]
32
+ ```
33
+
34
+ ## Установка и импорт
35
+
36
+ ```bash
37
+ npm install @ryuzaki13/react-foundation-api @ryuzaki13/react-foundation-lib fast-xml-parser
38
+ ```
39
+
40
+ ```ts
41
+ import { fetchUserTransports, parseUserTransportsXml } from "@ryuzaki13/react-foundation-api/adt";
42
+
43
+ import type { UserTransport } from "@ryuzaki13/react-foundation-api/adt";
44
+ ```
45
+
46
+ ## Быстрый старт
47
+
48
+ ```ts
49
+ async function loadTransports() {
50
+ try {
51
+ const transports = await fetchUserTransports();
52
+ return transports;
53
+ } catch (error) {
54
+ console.error("Не удалось получить transports", error);
55
+ return [];
56
+ }
57
+ }
58
+ ```
59
+
60
+ `fetchUserTransports` возвращает Promise. Пока он выполняется, данных ещё нет; ошибку нужно обработать через `try/catch`, TanStack Query или error boundary владельца сценария.
61
+
62
+ ## `fetchUserTransports`
63
+
64
+ Функция выполняет GET:
65
+
66
+ ```text
67
+ /sap/bc/adt/cts/transports?_action=FIND&trfunction=K
68
+ ```
69
+
70
+ Запрос отправляется через SAP/OData transport пакета с:
71
+
72
+ - `Accept: application/xml, text/xml, */*`;
73
+ - cookies через `credentials: include`;
74
+ - SAP language/client и SSO policy общего OData transport;
75
+ - пустым base URL, потому что ADT endpoint уже содержит `/sap/...`.
76
+
77
+ Ответ читается как text и передаётся `parseUserTransportsXml`.
78
+
79
+ Если сервер вместо XML вернул HTML login/error page, функция выбросит ошибку. Это защищает UI от ситуации, когда HTML ошибочно принимается за пустой список transports.
80
+
81
+ ## `parseUserTransportsXml`
82
+
83
+ Parser можно использовать отдельно, например в тесте или при получении XML другим transport:
84
+
85
+ ```ts
86
+ const xml = `
87
+ <root>
88
+ <CTS_REQ_HEADER>
89
+ <TRKORR>DEVK900001</TRKORR>
90
+ <AS4TEXT>Исправление отчёта</AS4TEXT>
91
+ <AS4USER>IVANOV</AS4USER>
92
+ <TARSYSTEM>QAS</TARSYSTEM>
93
+ <TRFUNCTION>K</TRFUNCTION>
94
+ <TRSTATUS>D</TRSTATUS>
95
+ </CTS_REQ_HEADER>
96
+ </root>`;
97
+
98
+ const transports = parseUserTransportsXml(xml);
99
+ ```
100
+
101
+ Алгоритм:
102
+
103
+ 1. Пустая или состоящая из пробелов строка превращается в `[]`.
104
+ 2. XML разбирается `fast-xml-parser`.
105
+ 3. Во вложенной структуре рекурсивно ищутся узлы `CTS_REQ_HEADER`.
106
+ 4. Поля преобразуются в `UserTransport`.
107
+ 5. Записи без непустого `TRKORR` отбрасываются.
108
+ 6. Дубликаты `transportNo` удаляются; сохраняется первая запись.
109
+ 7. Если header-узлы не найдены, parser ищет вложенные `TRKORR` и создаёт минимальные записи.
110
+
111
+ Parser делает `trim` строковых полей, но не декодирует бизнес-значения статуса/function и не проверяет их по справочнику SAP.
112
+
113
+ Некорректный XML может привести к исключению parser-а. Функция не заменяет его пустым массивом: повреждённый ответ должен быть виден как ошибка.
114
+
115
+ ## Формат результата
116
+
117
+ ```ts
118
+ interface UserTransport {
119
+ transportNo: string;
120
+ description: string | undefined;
121
+ owner: string | undefined;
122
+ targetSystem: string | undefined;
123
+ functionCode: string | undefined;
124
+ statusCode: string | undefined;
125
+ parentTransportNo: string | undefined;
126
+ }
127
+ ```
128
+
129
+ Соответствие XML:
130
+
131
+ | XML | Поле | Значение |
132
+ | --- | --- | --- |
133
+ | `TRKORR` | `transportNo` | Обязательный номер transport |
134
+ | `AS4TEXT` | `description` | Описание |
135
+ | `AS4USER` | `owner` | Владелец |
136
+ | `TARSYSTEM` | `targetSystem` | Целевая система |
137
+ | `TRFUNCTION` | `functionCode` | SAP transport function code |
138
+ | `TRSTATUS` | `statusCode` | SAP status code |
139
+ | `STRKORR` | `parentTransportNo` | Родительский request/task |
140
+
141
+ Optional-поле может быть `undefined`. Проверяйте его перед отображением:
142
+
143
+ ```ts
144
+ const label = transport.description
145
+ ? `${transport.transportNo} — ${transport.description}`
146
+ : transport.transportNo;
147
+ ```
148
+
149
+ ## Как устроен ADT response
150
+
151
+ ADT endpoint возвращает не OData JSON, а ABAP XML. Реальный документ обычно содержит namespace и несколько служебных обёрток:
152
+
153
+ ```xml
154
+ <?xml version="1.0" encoding="UTF-8"?>
155
+ <asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
156
+ <asx:values>
157
+ <DATA>
158
+ <CTS_REQ_HEADER>
159
+ <TRKORR>DEVK900001</TRKORR>
160
+ <AS4TEXT>Исправление отчёта</AS4TEXT>
161
+ <AS4USER>IVANOV</AS4USER>
162
+ <TARSYSTEM>QAS</TARSYSTEM>
163
+ <TRFUNCTION>K</TRFUNCTION>
164
+ <TRSTATUS>D</TRSTATUS>
165
+ <STRKORR>DEVK900000</STRKORR>
166
+ </CTS_REQ_HEADER>
167
+ </DATA>
168
+ </asx:values>
169
+ </asx:abap>
170
+ ```
171
+
172
+ Parser намеренно не зависит от точного положения `CTS_REQ_HEADER`: он рекурсивно обходит objects и arrays. Поэтому дополнительная server wrapper-структура не ломает parsing, пока имена полезных tags остаются прежними.
173
+
174
+ ### Request parameters endpoint
175
+
176
+ ```text
177
+ _action=FIND
178
+ trfunction=K
179
+ ```
180
+
181
+ Они зафиксированы внутри `fetchUserTransports`. Consumer не может изменить owner, function, status или paging через options. Если нужен другой ADT query, создайте отдельный API у владельца package, а не подменяйте поведение этой функции string manipulation-ом.
182
+
183
+ ### Почему используется общий SAP transport
184
+
185
+ Хотя response не OData, `fetchUserTransports` использует `fetchBase` из `/odata`, потому что ему нужны те же infrastructure concerns:
186
+
187
+ - cookies текущей SAP session;
188
+ - `sap-language` и configured `sap-client`;
189
+ - manual redirect detection;
190
+ - automatic SSO recovery;
191
+ - единая HTTP error policy.
192
+
193
+ Base URL передаётся как пустая строка, потому что path уже начинается с `/sap/bc/adt`.
194
+
195
+ ## Подробный алгоритм parser-а
196
+
197
+ ### 1. Пустой документ
198
+
199
+ ```ts
200
+ parseUserTransportsXml("");
201
+ // []
202
+
203
+ parseUserTransportsXml(" \n\t");
204
+ // []
205
+ ```
206
+
207
+ Whitespace-only input не отправляется в XML parser.
208
+
209
+ ### 2. XML остаётся строковым
210
+
211
+ `fast-xml-parser` настроен с `parseTagValue: false`. Значения вроде `0001`, `D` или transport number не превращаются автоматически в numbers/booleans.
212
+
213
+ Это важно:
214
+
215
+ ```xml
216
+ <TRKORR>DEVK900001</TRKORR>
217
+ ```
218
+
219
+ должен остаться exact identifier, а не пройти numeric coercion.
220
+
221
+ ### 3. Поиск всех headers
222
+
223
+ Parser собирает каждый nested value по key `CTS_REQ_HEADER`. Один XML parser может представить:
224
+
225
+ - один header как object;
226
+ - несколько соседних headers как array;
227
+ - headers внутри дополнительных wrappers.
228
+
229
+ Оба случая нормализуются к одному массиву.
230
+
231
+ ### 4. Runtime narrowing
232
+
233
+ Любой найденный value сначала проверяется как record. Primitive/null не превращаются в transport.
234
+
235
+ ### 5. Нормализация text
236
+
237
+ Все SAP fields проходят text normalization:
238
+
239
+ ```text
240
+ " DEVK900001 " -> "DEVK900001"
241
+ " " -> undefined
242
+ null -> undefined
243
+ ```
244
+
245
+ Запись без `transportNo` отбрасывается полностью.
246
+
247
+ ### 6. Дедупликация
248
+
249
+ ```ts
250
+ parseUserTransportsXml(`
251
+ <root>
252
+ <CTS_REQ_HEADER>
253
+ <TRKORR>DEVK900001</TRKORR>
254
+ <AS4TEXT>Первое описание</AS4TEXT>
255
+ </CTS_REQ_HEADER>
256
+ <CTS_REQ_HEADER>
257
+ <TRKORR>DEVK900001</TRKORR>
258
+ <AS4TEXT>Второе описание</AS4TEXT>
259
+ </CTS_REQ_HEADER>
260
+ </root>
261
+ `);
262
+ ```
263
+
264
+ Вернёт первую запись. Parser не объединяет optional fields дубликатов.
265
+
266
+ ### 7. Fallback без `CTS_REQ_HEADER`
267
+
268
+ Некоторые responses содержат только nested `TRKORR`. Тогда parser формирует минимальные records:
269
+
270
+ ```ts
271
+ parseUserTransportsXml(`
272
+ <root>
273
+ <RESULT><TRKORR>DEVK900003</TRKORR></RESULT>
274
+ <RESULT><TRKORR>DEVK900004</TRKORR></RESULT>
275
+ </root>
276
+ `);
277
+ ```
278
+
279
+ ```ts
280
+ [
281
+ {
282
+ transportNo: "DEVK900003",
283
+ description: undefined,
284
+ owner: undefined,
285
+ targetSystem: undefined,
286
+ functionCode: undefined,
287
+ statusCode: undefined,
288
+ parentTransportNo: undefined
289
+ },
290
+ // ...
291
+ ]
292
+ ```
293
+
294
+ Fallback включается только если ни одного корректного header record не найдено.
295
+
296
+ ## Использование с TanStack Query
297
+
298
+ Модуль не навязывает query key/stale policy. Их определяет feature:
299
+
300
+ ```tsx
301
+ import { queryOptions, useQuery } from "@tanstack/react-query";
302
+ import { fetchUserTransports } from "@ryuzaki13/react-foundation-api/adt";
303
+
304
+ const adtTransportKeys = {
305
+ all: ["adt-transports"] as const,
306
+ list: () => [...adtTransportKeys.all, "list"] as const
307
+ };
308
+
309
+ export const adtTransportsQueryOptions = () =>
310
+ queryOptions({
311
+ queryKey: adtTransportKeys.list(),
312
+ queryFn: fetchUserTransports,
313
+ staleTime: 5 * 60 * 1000
314
+ });
315
+
316
+ function TransportSelect() {
317
+ const query = useQuery(adtTransportsQueryOptions());
318
+
319
+ if (query.isPending) return <p>Загрузка транспортов…</p>;
320
+ if (query.isError) return <p>{query.error.message}</p>;
321
+ if (query.data.length === 0) return <p>Доступных транспортов нет</p>;
322
+
323
+ return (
324
+ <select>
325
+ {query.data.map((item) => (
326
+ <option key={item.transportNo} value={item.transportNo}>
327
+ {item.transportNo}
328
+ {item.description ? ` — ${item.description}` : ""}
329
+ </option>
330
+ ))}
331
+ </select>
332
+ );
333
+ }
334
+ ```
335
+
336
+ `fetchUserTransports` не принимает `AbortSignal`, поэтому query cancellation не прерывает underlying fetch через этот API. Не обещайте UI мгновенный transport abort. Если это станет обязательным, контракт должен быть расширен в package.
337
+
338
+ Query key не должен содержать user secrets. Если список зависит от logged-in user, смена user/session должна сопровождаться очисткой auth-sensitive Query cache на project boundary.
339
+
340
+ ## Parent request и task
341
+
342
+ `parentTransportNo` обычно помогает отличить task от верхнеуровневого request:
343
+
344
+ ```ts
345
+ function getTransportLabel(transport: UserTransport) {
346
+ const kind = transport.parentTransportNo ? "Задача" : "Запрос";
347
+ return `${kind}: ${transport.transportNo}`;
348
+ }
349
+ ```
350
+
351
+ Это только presentation inference. Модуль не интерпретирует SAP status/function codes и не гарантирует бизнес-классификацию. Для правил конкретной системы используйте domain mapping приложения.
352
+
353
+ ## Тестирование
354
+
355
+ Parser — pure function, поэтому тестируйте fixtures без network:
356
+
357
+ ```ts
358
+ import { describe, expect, it } from "vitest";
359
+ import { parseUserTransportsXml } from "@ryuzaki13/react-foundation-api/adt";
360
+
361
+ it("сохраняет leading zeros и optional fields", () => {
362
+ const result = parseUserTransportsXml(`
363
+ <root>
364
+ <CTS_REQ_HEADER>
365
+ <TRKORR>DEVK900001</TRKORR>
366
+ <TRSTATUS>D</TRSTATUS>
367
+ </CTS_REQ_HEADER>
368
+ </root>
369
+ `);
370
+
371
+ expect(result).toEqual([
372
+ {
373
+ transportNo: "DEVK900001",
374
+ description: undefined,
375
+ owner: undefined,
376
+ targetSystem: undefined,
377
+ functionCode: undefined,
378
+ statusCode: "D",
379
+ parentTransportNo: undefined
380
+ }
381
+ ]);
382
+ });
383
+ ```
384
+
385
+ Минимальный набор cases:
386
+
387
+ - single header;
388
+ - array headers;
389
+ - nested wrappers/namespaces;
390
+ - duplicate number;
391
+ - whitespace fields;
392
+ - empty XML;
393
+ - fallback `TRKORR`;
394
+ - malformed XML и ожидаемая ошибка.
395
+
396
+ Для transport integration mock-айте `fetch`/MSW и проверяйте Content-Type `text/html`, XML body и HTTP error. Не используйте real SAP credentials в tests.
397
+
398
+ ## Частые ошибки
399
+
400
+ ### Использовать поля `function` и `status`
401
+
402
+ Правильные имена public contract — `functionCode` и `statusCode`.
403
+
404
+ ### Считать status code готовым label
405
+
406
+ `"D"` остаётся строкой `"D"`. Расшифровка зависит от SAP contract проекта и не выполняется package-ом.
407
+
408
+ ### Принимать пустой array за любую ошибку
409
+
410
+ Пустой или whitespace XML даёт `[]`, но HTTP, HTML и malformed XML errors выбрасываются. Обрабатывайте error отдельно от empty state.
411
+
412
+ ### Объединять ADT и UI2 records без mapper-а
413
+
414
+ `/adt` и `/transport` имеют разные source contracts. Если feature показывает их вместе, создайте явную domain model и collision policy.
415
+
416
+ ### Deep import parser-а
417
+
418
+ Используйте только:
419
+
420
+ ```ts
421
+ import { parseUserTransportsXml } from "@ryuzaki13/react-foundation-api/adt";
422
+ ```
423
+
424
+ ## Ошибки и ограничения
425
+
426
+ - `fast-xml-parser` должен быть установлен в host, использующем этот entrypoint.
427
+ - Это не generic ADT client: endpoint и query parameters зафиксированы.
428
+ - Функция не создаёт/не освобождает transport и не проверяет authorization.
429
+ - SSO/cookies требуют browser/proxy окружения SAP. На server runtime поведение зависит от переданного глобального `fetch` и cookie boundary.
430
+ - HTML-response, HTTP error и XML parse error не превращаются в `[]`.
431
+ - Дедупликация выполняется только по `transportNo` и сохраняет первую найденную запись.
432
+
433
+ ## FAQ
434
+
435
+ ### Можно ли передать другой endpoint?
436
+
437
+ Нет. `fetchUserTransports` — конкретный ADT use case с фиксированным endpoint.
438
+
439
+ ### Можно ли получить только transports конкретного owner?
440
+
441
+ Не через текущий public API. Фильтруйте result локально либо расширьте package contract, если server-side filter является общей потребностью.
442
+
443
+ ### Parser меняет исходную XML string?
444
+
445
+ Нет. Он создаёт новые JS objects.
446
+
447
+ ### Почему optional fields явно содержат `undefined`?
448
+
449
+ Таков стабильный `UserTransport` shape. Consumer может безопасно destructure поля без проверки их наличия как properties.
450
+
451
+ ### Работает ли в Node?
452
+
453
+ Pure parser работает при наличии `fast-xml-parser`. Network function зависит от общего SAP transport, URL/cookies/build constants и в первую очередь рассчитана на browser application.
454
+
455
+ ## Полный API
456
+
457
+ | Export | Назначение |
458
+ | --- | --- |
459
+ | `fetchUserTransports` | Загружает и разбирает ADT XML |
460
+ | `parseUserTransportsXml` | Преобразует XML string в массив |
461
+ | `UserTransport` | Контракт результата |