@ryuzaki13/react-foundation-api 1.1.17 → 1.1.19
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/package.json +1 -1
- package/src/adt/README.mdx +305 -8
- package/src/async/README.mdx +375 -0
- package/src/error-report/README.mdx +323 -0
- package/src/foundationApi.mdx +2 -8
- package/src/http/README.mdx +349 -0
- package/src/odata/README.mdx +4907 -555
- package/src/persisted/README.mdx +626 -0
- package/src/resource/README.mdx +462 -0
- package/src/server-fn/README.mdx +402 -0
- package/src/transport/README.mdx +345 -0
package/package.json
CHANGED
package/src/adt/README.mdx
CHANGED
|
@@ -117,12 +117,12 @@ Parser делает `trim` строковых полей, но не декоди
|
|
|
117
117
|
```ts
|
|
118
118
|
interface UserTransport {
|
|
119
119
|
transportNo: string;
|
|
120
|
-
description
|
|
121
|
-
owner
|
|
122
|
-
targetSystem
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
parentTransportNo
|
|
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
126
|
}
|
|
127
127
|
```
|
|
128
128
|
|
|
@@ -134,8 +134,8 @@ interface UserTransport {
|
|
|
134
134
|
| `AS4TEXT` | `description` | Описание |
|
|
135
135
|
| `AS4USER` | `owner` | Владелец |
|
|
136
136
|
| `TARSYSTEM` | `targetSystem` | Целевая система |
|
|
137
|
-
| `TRFUNCTION` | `
|
|
138
|
-
| `TRSTATUS` | `
|
|
137
|
+
| `TRFUNCTION` | `functionCode` | SAP transport function code |
|
|
138
|
+
| `TRSTATUS` | `statusCode` | SAP status code |
|
|
139
139
|
| `STRKORR` | `parentTransportNo` | Родительский request/task |
|
|
140
140
|
|
|
141
141
|
Optional-поле может быть `undefined`. Проверяйте его перед отображением:
|
|
@@ -146,6 +146,281 @@ const label = transport.description
|
|
|
146
146
|
: transport.transportNo;
|
|
147
147
|
```
|
|
148
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
|
+
|
|
149
424
|
## Ошибки и ограничения
|
|
150
425
|
|
|
151
426
|
- `fast-xml-parser` должен быть установлен в host, использующем этот entrypoint.
|
|
@@ -155,6 +430,28 @@ const label = transport.description
|
|
|
155
430
|
- HTML-response, HTTP error и XML parse error не превращаются в `[]`.
|
|
156
431
|
- Дедупликация выполняется только по `transportNo` и сохраняет первую найденную запись.
|
|
157
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
|
+
|
|
158
455
|
## Полный API
|
|
159
456
|
|
|
160
457
|
| Export | Назначение |
|