@ryuzaki13/react-foundation-api 1.1.9 → 1.1.11
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/dist/odata/index.d.ts +0 -1
- package/dist/odata/index.d.ts.map +1 -1
- package/dist/odata/index.js +88 -96
- package/dist/odata/index.js.map +1 -1
- package/package.json +3 -1
- package/src/README.md +937 -0
- package/src/async/README.md +623 -0
- package/src/async/async.mdx +6 -0
- package/src/odata/README.md +761 -0
- package/src/odata/odataFetchFn.mdx +6 -0
- package/src/persisted/README.md +598 -0
- package/src/persisted/persisted.mdx +6 -0
- package/dist/odata/useTextServiceQuery.d.ts +0 -3
- package/dist/odata/useTextServiceQuery.d.ts.map +0 -1
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
# OData API Helpers
|
|
2
|
+
|
|
3
|
+
Этот каталог содержит helper-функции для выполнения OData-запросов с опорой на metadata сервиса.
|
|
4
|
+
|
|
5
|
+
Для прикладного кода доступны отдельные функции под разные сценарии:
|
|
6
|
+
|
|
7
|
+
- `odataCreateFn`
|
|
8
|
+
- `odataUpdateFn`
|
|
9
|
+
- `odataDeleteFn`
|
|
10
|
+
- `odataReadFn`
|
|
11
|
+
- `odataQueryFn`
|
|
12
|
+
- `odataFunctionImportFn`
|
|
13
|
+
|
|
14
|
+
Все они используют общий низкоуровневый helper `odataFetchFn`.
|
|
15
|
+
|
|
16
|
+
Важно:
|
|
17
|
+
|
|
18
|
+
- через публичный barrel `@/shared/api` доступны специализированные helper-ы;
|
|
19
|
+
- `odataFetchFn` не реэкспортируется из публичного barrel и обычно импортируется напрямую только для низкоуровневых сценариев.
|
|
20
|
+
|
|
21
|
+
Механизм кеширования `$metadata`, version-check, справочников `useODataCollectionQuery` и critical update описан отдельно в [docs/odata-reference-cache.md](/docs/odata-reference-cache.md).
|
|
22
|
+
|
|
23
|
+
## Что делают helper-ы
|
|
24
|
+
|
|
25
|
+
Все OData helper-ы:
|
|
26
|
+
|
|
27
|
+
- загружают metadata OData-сервиса через `react-query`;
|
|
28
|
+
- находят `target` в metadata;
|
|
29
|
+
- проверяют совместимость операции и target;
|
|
30
|
+
- вычисляют HTTP-метод;
|
|
31
|
+
- строят URL по metadata;
|
|
32
|
+
- объединяют `params` target и OData query options;
|
|
33
|
+
- сериализуют `body`, если он есть;
|
|
34
|
+
- могут автоматически распарсить ответ по metadata;
|
|
35
|
+
- могут преобразовать raw-ответ через `transform`.
|
|
36
|
+
|
|
37
|
+
## Публичный API
|
|
38
|
+
|
|
39
|
+
Из `index.ts` наружу экспортируются:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
export { odataFetch, type ODataFetchOptions } from "./odataFetch";
|
|
43
|
+
export {
|
|
44
|
+
odataCreateFn,
|
|
45
|
+
odataDeleteFn,
|
|
46
|
+
odataFunctionImportFn,
|
|
47
|
+
odataQueryFn,
|
|
48
|
+
odataReadFn,
|
|
49
|
+
odataUpdateFn,
|
|
50
|
+
type ODataFetchFnRequest
|
|
51
|
+
} from "./odataQueryFn";
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Общая модель вызова
|
|
55
|
+
|
|
56
|
+
Каждый helper вызывается в два шага:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const queryFn = odataQueryFn({
|
|
60
|
+
odata: { service: "TEXT_DEMO_SRV", target: "TextEntity" }
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const result = await queryFn({ client, signal });
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
На первом шаге вы описываете запрос.
|
|
67
|
+
На втором шаге вызываете функцию с runtime-контекстом:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
{
|
|
71
|
+
client: QueryClient;
|
|
72
|
+
signal?: AbortSignal;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- `client` обязателен, потому что metadata читается через `react-query`;
|
|
77
|
+
- `signal` обычно приходит из `useQuery`, `useInfiniteQuery` или `useMutation`.
|
|
78
|
+
|
|
79
|
+
## Generics
|
|
80
|
+
|
|
81
|
+
Все helper-ы используют одну и ту же generic-модель:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
<I, O = I, T = I>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Перед чтением generics полезно держать в голове простое правило:
|
|
88
|
+
|
|
89
|
+
- `I` обычно описывает одну OData-сущность в том виде, в котором она опубликована в metadata;
|
|
90
|
+
- на практике это прикладной TypeScript-тип для `EntityType`, построенный по полям metadata соответствующей сущности;
|
|
91
|
+
- `O` описывает доменную модель после преобразования;
|
|
92
|
+
- `T` используется только там, где нужно отдельно типизировать `body` у `create/update`.
|
|
93
|
+
|
|
94
|
+
### `I`
|
|
95
|
+
|
|
96
|
+
`I` — тип одной сущности в OData-форме.
|
|
97
|
+
|
|
98
|
+
Проще всего думать о нём как о TypeScript-представлении `EntityType` из metadata.
|
|
99
|
+
|
|
100
|
+
Пример:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
type RawRow = {
|
|
104
|
+
ID: string;
|
|
105
|
+
NAME: string;
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `O`
|
|
110
|
+
|
|
111
|
+
`O` — итоговый тип после `transform`.
|
|
112
|
+
|
|
113
|
+
Для `odataCreateFn`, `odataUpdateFn`, `odataDeleteFn`, `odataReadFn`, `odataFunctionImportFn`:
|
|
114
|
+
|
|
115
|
+
- без `transform` результат имеет тип `I`;
|
|
116
|
+
- с `transform` результат имеет тип `O`.
|
|
117
|
+
|
|
118
|
+
Для `odataQueryFn`:
|
|
119
|
+
|
|
120
|
+
- без `transform` результат имеет тип `I[]`;
|
|
121
|
+
- `transform` получает `I[]`;
|
|
122
|
+
- `transform` может вернуть `O[]` или один `O`.
|
|
123
|
+
|
|
124
|
+
### `T`
|
|
125
|
+
|
|
126
|
+
`T` нужен только для `odataCreateFn` и `odataUpdateFn`, если тип тела запроса нужно описать отдельно от raw-ответа.
|
|
127
|
+
|
|
128
|
+
`options` всегда типизируются по `I`, потому что:
|
|
129
|
+
|
|
130
|
+
- `select`
|
|
131
|
+
- `expand`
|
|
132
|
+
- `sorts`
|
|
133
|
+
- `expression`
|
|
134
|
+
|
|
135
|
+
должны ссылаться на поля OData `EntityType`, опубликованные в metadata.
|
|
136
|
+
|
|
137
|
+
### Когда достаточно одного generic
|
|
138
|
+
|
|
139
|
+
Если:
|
|
140
|
+
|
|
141
|
+
- ответ не трансформируется;
|
|
142
|
+
- `options` описывают поля raw-сущности;
|
|
143
|
+
- `body` совпадает по форме с raw-ответом;
|
|
144
|
+
|
|
145
|
+
то обычно достаточно одного generic:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
const queryFn = odataQueryFn<RawRow>({
|
|
149
|
+
odata: { service: "TEXT_DEMO_SRV", target: "TextEntity" }
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Когда нужен `O`
|
|
154
|
+
|
|
155
|
+
Если вы используете `transform`, укажите тип результата:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
type Row = {
|
|
159
|
+
id: string;
|
|
160
|
+
name: string;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const queryFn = odataQueryFn<RawRow, Row>({
|
|
164
|
+
odata: { service: "TEXT_DEMO_SRV", target: "TextEntity" },
|
|
165
|
+
transform: (rows) =>
|
|
166
|
+
rows.map((row) => ({
|
|
167
|
+
id: row.ID,
|
|
168
|
+
name: row.NAME
|
|
169
|
+
}))
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Когда нужен `T`
|
|
174
|
+
|
|
175
|
+
Для `odataCreateFn` и `odataUpdateFn` `body` типизируется через `T`.
|
|
176
|
+
|
|
177
|
+
Если тело отличается от raw-ответа, укажите третий generic:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
type CreateBody = {
|
|
181
|
+
name: string;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
type RawResponse = {
|
|
185
|
+
id: string;
|
|
186
|
+
name: string;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const mutationFn = odataCreateFn<RawResponse, RawResponse, CreateBody>({
|
|
190
|
+
odata: { service: "TEXT_DEMO_SRV", target: "ENTITY" },
|
|
191
|
+
body: { name: "Demo" }
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Общие поля конфигурации
|
|
196
|
+
|
|
197
|
+
## `odata`
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
odata: {
|
|
201
|
+
service: string;
|
|
202
|
+
target: string;
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
- `service` — имя OData-сервиса;
|
|
207
|
+
- `target` — имя target ровно в том виде, как оно опубликовано в metadata.
|
|
208
|
+
|
|
209
|
+
`target` может быть:
|
|
210
|
+
|
|
211
|
+
- обычной `Entity`;
|
|
212
|
+
- `FunctionImport`.
|
|
213
|
+
|
|
214
|
+
## `options`
|
|
215
|
+
|
|
216
|
+
Тип: `ODataFetchOptions<I>`.
|
|
217
|
+
|
|
218
|
+
Это дополнительные OData query options:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
options?: {
|
|
222
|
+
expression?: FilterExpression<T>;
|
|
223
|
+
sorts?: Sort<keyof T>[];
|
|
224
|
+
select?: (keyof T)[];
|
|
225
|
+
expand?: (keyof T)[];
|
|
226
|
+
top?: number;
|
|
227
|
+
skip?: number;
|
|
228
|
+
inlinecount?: string;
|
|
229
|
+
format?: "json";
|
|
230
|
+
baseUrl?: BaseURLType;
|
|
231
|
+
swCache?: string;
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Они преобразуются в стандартные OData query-параметры:
|
|
236
|
+
|
|
237
|
+
- `$filter`
|
|
238
|
+
- `$orderby`
|
|
239
|
+
- `$select`
|
|
240
|
+
- `$expand`
|
|
241
|
+
- `$top`
|
|
242
|
+
- `$skip`
|
|
243
|
+
- `$inlinecount`
|
|
244
|
+
|
|
245
|
+
Пример:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
options: {
|
|
249
|
+
select: ["ID", "NAME"],
|
|
250
|
+
top: 20,
|
|
251
|
+
sorts: [{ key: "NAME", desc: false }]
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## `params`
|
|
256
|
+
|
|
257
|
+
`params` — это параметры самого target, описанные в metadata.
|
|
258
|
+
|
|
259
|
+
Тип:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
type ODataParameters = Partial<Record<string, ODataValue>>;
|
|
263
|
+
|
|
264
|
+
type ODataValue = {
|
|
265
|
+
value: InputType;
|
|
266
|
+
formatter?: ODataFormatterFn;
|
|
267
|
+
};
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Пример:
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
params: {
|
|
274
|
+
p_date: { value: new Date() },
|
|
275
|
+
p_customer: { value: "100015" }
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Эти параметры участвуют в построении URL target.
|
|
280
|
+
|
|
281
|
+
## `init`
|
|
282
|
+
|
|
283
|
+
Тип:
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
init?: Omit<RequestInit, "signal" | "method" | "body">
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Сюда можно передавать дополнительные fetch-опции, кроме:
|
|
290
|
+
|
|
291
|
+
- `signal`
|
|
292
|
+
- `method`
|
|
293
|
+
- `body`
|
|
294
|
+
|
|
295
|
+
Обычно через `init` передают:
|
|
296
|
+
|
|
297
|
+
- `headers`
|
|
298
|
+
- `credentials`
|
|
299
|
+
- `mode`
|
|
300
|
+
- `cache`
|
|
301
|
+
|
|
302
|
+
## `transform`
|
|
303
|
+
|
|
304
|
+
Для single-result helper-ов:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
transform?: (data: I, target: ODataTargetMetadata) => O;
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Для `odataQueryFn`:
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
transform?: (data: I[], target: ODataTargetMetadata) => O[] | O;
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
`transform` нужен для преобразования raw-ответа в доменную модель.
|
|
317
|
+
|
|
318
|
+
Пример:
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
transform: (rows) =>
|
|
322
|
+
rows.map((row) => ({
|
|
323
|
+
id: row.ID,
|
|
324
|
+
name: row.NAME
|
|
325
|
+
}));
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## `autoParse`
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
autoParse?: boolean;
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Если включить `autoParse`, ответ сначала проходит через `parseODataResponseByMetadata`, а затем через `transform`.
|
|
335
|
+
|
|
336
|
+
Автоматически парсятся:
|
|
337
|
+
|
|
338
|
+
- `abapBooleanLike` значения;
|
|
339
|
+
- даты;
|
|
340
|
+
- числа;
|
|
341
|
+
- другие primitive-типы, если они известны из metadata.
|
|
342
|
+
|
|
343
|
+
Для `FunctionImport` автопарсинг работает, когда metadata указывает `resultEntity`.
|
|
344
|
+
|
|
345
|
+
Порядок обработки:
|
|
346
|
+
|
|
347
|
+
1. выполняется запрос;
|
|
348
|
+
2. приходит raw-ответ;
|
|
349
|
+
3. применяется `autoParse`;
|
|
350
|
+
4. применяется `transform`;
|
|
351
|
+
5. возвращается `{ data, totalCount }`.
|
|
352
|
+
|
|
353
|
+
## `swCache`
|
|
354
|
+
|
|
355
|
+
Политика кеширования для Service Worker.
|
|
356
|
+
|
|
357
|
+
Прокидывается через заголовок `x-sw-cache`.
|
|
358
|
+
|
|
359
|
+
Поддерживаемые примеры:
|
|
360
|
+
|
|
361
|
+
- `"off"`
|
|
362
|
+
- `"ttl=24h"`
|
|
363
|
+
- `"ttl=6h;name=ref"`
|
|
364
|
+
- `"ttl=10m;max=200;name=ui"`
|
|
365
|
+
- `"ttl=30s;max=300;name=fast"`
|
|
366
|
+
- `"bust=24h;name=ref"`
|
|
367
|
+
|
|
368
|
+
## `params` и `options` — это разные уровни
|
|
369
|
+
|
|
370
|
+
- `params` описывают сам target;
|
|
371
|
+
- `options` описывают способ чтения результата.
|
|
372
|
+
|
|
373
|
+
Проще говоря:
|
|
374
|
+
|
|
375
|
+
- `params` влияют на path target;
|
|
376
|
+
- `options` влияют на `$filter`, `$select`, `$top` и другие OData query options.
|
|
377
|
+
|
|
378
|
+
## Helper `wrapODataParams`
|
|
379
|
+
|
|
380
|
+
Часто параметры удобно собирать через `wrapODataParams`:
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
import { wrapODataParams } from "@ryuzaki13/react-foundation-lib/odata-service";
|
|
384
|
+
|
|
385
|
+
params: wrapODataParams({
|
|
386
|
+
variantId: "uuid-1",
|
|
387
|
+
appId: "app"
|
|
388
|
+
});
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## Матрица операций
|
|
392
|
+
|
|
393
|
+
| Функция | Target | HTTP | `params` | `body` | URL |
|
|
394
|
+
| ----------------------- | ------------------------- | ----------- | ---------------------------- | ---------- | ----------------------------------------- |
|
|
395
|
+
| `odataCreateFn` | `Entity` | `POST` | нет | обязателен | `/SERVICE/ENTITY` |
|
|
396
|
+
| `odataUpdateFn` | `Entity` | `PUT` | обязательны | обязателен | `/SERVICE/ENTITY(params...)` |
|
|
397
|
+
| `odataDeleteFn` | `Entity` | `DELETE` | обязательны | нет | `/SERVICE/ENTITY(params...)` |
|
|
398
|
+
| `odataReadFn` | `Entity` | `GET` | обязательны | нет | `/SERVICE/ENTITY(params...)` |
|
|
399
|
+
| `odataQueryFn` | plain `Entity` | `GET` | опциональны, но игнорируются | нет | `/SERVICE/ENTITY` |
|
|
400
|
+
| `odataQueryFn` | query `Entity` с `result` | `GET` | участвуют в URL | нет | `/SERVICE/ENTITY(params...)/Set\|Results` |
|
|
401
|
+
| `odataFunctionImportFn` | `FunctionImport` | из metadata | обязательны | нет | `/SERVICE/FI?param=...` |
|
|
402
|
+
|
|
403
|
+
## `odataCreateFn`
|
|
404
|
+
|
|
405
|
+
Используйте для создания сущности.
|
|
406
|
+
|
|
407
|
+
Конфигурация:
|
|
408
|
+
|
|
409
|
+
```ts
|
|
410
|
+
{
|
|
411
|
+
odata: ODataServiceConfig;
|
|
412
|
+
body: T;
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Поведение:
|
|
417
|
+
|
|
418
|
+
- только для `Entity`;
|
|
419
|
+
- HTTP-метод: `POST`;
|
|
420
|
+
- путь: `/SERVICE/ENTITY`;
|
|
421
|
+
- `body` обязателен;
|
|
422
|
+
- `params` не используются.
|
|
423
|
+
|
|
424
|
+
Пример:
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
const mutationFn = odataCreateFn<Variant>({
|
|
428
|
+
odata: { service: "TEXT_CONFIG_SRV", target: "TEXT_VARIANT" },
|
|
429
|
+
body: {
|
|
430
|
+
id: "uuid-4",
|
|
431
|
+
name: "Новый вариант"
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## `odataUpdateFn`
|
|
437
|
+
|
|
438
|
+
Используйте для обновления сущности по ключу.
|
|
439
|
+
|
|
440
|
+
Конфигурация:
|
|
441
|
+
|
|
442
|
+
```ts
|
|
443
|
+
{
|
|
444
|
+
odata: ODataServiceConfig;
|
|
445
|
+
params: ODataParameters;
|
|
446
|
+
body: T;
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Поведение:
|
|
451
|
+
|
|
452
|
+
- только для `Entity`;
|
|
453
|
+
- HTTP-метод: `PUT`;
|
|
454
|
+
- путь: `/SERVICE/ENTITY(params...)`;
|
|
455
|
+
- `params` обязательны;
|
|
456
|
+
- `body` обязателен.
|
|
457
|
+
|
|
458
|
+
Пример:
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
const mutationFn = odataUpdateFn<ThemeRaw, ThemeRaw, { name: string }>({
|
|
462
|
+
odata: { service: "TEXT_CONFIG_SRV", target: "TEXT_THEME" },
|
|
463
|
+
params: wrapODataParams({ name: "dark" }),
|
|
464
|
+
body: { name: "dark" }
|
|
465
|
+
});
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## `odataDeleteFn`
|
|
469
|
+
|
|
470
|
+
Используйте для удаления сущности по ключу.
|
|
471
|
+
|
|
472
|
+
Конфигурация:
|
|
473
|
+
|
|
474
|
+
```ts
|
|
475
|
+
{
|
|
476
|
+
odata: ODataServiceConfig;
|
|
477
|
+
params: ODataParameters;
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Поведение:
|
|
482
|
+
|
|
483
|
+
- только для `Entity`;
|
|
484
|
+
- HTTP-метод: `DELETE`;
|
|
485
|
+
- путь: `/SERVICE/ENTITY(params...)`;
|
|
486
|
+
- `body` не используется.
|
|
487
|
+
|
|
488
|
+
Пример:
|
|
489
|
+
|
|
490
|
+
```ts
|
|
491
|
+
const mutationFn = odataDeleteFn({
|
|
492
|
+
odata: { service: "TEXT_CONFIG_SRV", target: "TextPresetSet" },
|
|
493
|
+
params: wrapODataParams({ recId: "uuid-1" })
|
|
494
|
+
});
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
## `odataReadFn`
|
|
498
|
+
|
|
499
|
+
Используйте для чтения одной сущности по ключу.
|
|
500
|
+
|
|
501
|
+
Конфигурация:
|
|
502
|
+
|
|
503
|
+
```ts
|
|
504
|
+
{
|
|
505
|
+
odata: ODataServiceConfig;
|
|
506
|
+
params: ODataParameters;
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Поведение:
|
|
511
|
+
|
|
512
|
+
- только для `Entity`;
|
|
513
|
+
- HTTP-метод: `GET`;
|
|
514
|
+
- путь: `/SERVICE/ENTITY(params...)`;
|
|
515
|
+
- `body` не поддерживается.
|
|
516
|
+
|
|
517
|
+
`odataReadFn` не подходит для query entity, у которой в metadata есть `result`.
|
|
518
|
+
Для таких target используйте `odataQueryFn`.
|
|
519
|
+
|
|
520
|
+
Пример:
|
|
521
|
+
|
|
522
|
+
```ts
|
|
523
|
+
const queryFn = odataReadFn<Variant>({
|
|
524
|
+
odata: { service: "TEXT_CONFIG_SRV", target: "TEXT_VARIANT" },
|
|
525
|
+
params: wrapODataParams({ variantId: "uuid-1" })
|
|
526
|
+
});
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
## `odataQueryFn`
|
|
530
|
+
|
|
531
|
+
Это основной helper для чтения списков и query-сущностей.
|
|
532
|
+
|
|
533
|
+
Конфигурация:
|
|
534
|
+
|
|
535
|
+
```ts
|
|
536
|
+
{
|
|
537
|
+
odata: ODataServiceConfig;
|
|
538
|
+
params?: ODataParameters;
|
|
539
|
+
options?: ODataFetchOptions<I>;
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Plain entity
|
|
544
|
+
|
|
545
|
+
Если target — обычная `Entity`, URL будет таким:
|
|
546
|
+
|
|
547
|
+
```txt
|
|
548
|
+
/SERVICE/ENTITY
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
Если для такого target передать `params`, они будут проигнорированы.
|
|
552
|
+
|
|
553
|
+
### Parameterized query entity
|
|
554
|
+
|
|
555
|
+
Если metadata содержит `result: "Set"` или `result: "Results"`, URL будет таким:
|
|
556
|
+
|
|
557
|
+
```txt
|
|
558
|
+
/SERVICE/ENTITY(params...)/Set
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
или:
|
|
562
|
+
|
|
563
|
+
```txt
|
|
564
|
+
/SERVICE/ENTITY(params...)/Results
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
В этом случае `params` участвуют в построении URL.
|
|
568
|
+
|
|
569
|
+
### Пример списка с transform
|
|
570
|
+
|
|
571
|
+
```ts
|
|
572
|
+
type RawRow = {
|
|
573
|
+
ID: string;
|
|
574
|
+
NAME: string;
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
type Row = {
|
|
578
|
+
id: string;
|
|
579
|
+
name: string;
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
const queryFn = odataQueryFn<RawRow, Row>({
|
|
583
|
+
odata: { service: "TEXT_REPORT_SRV", target: "TEXT_REPORT_ENTITY" },
|
|
584
|
+
params: wrapODataParams({
|
|
585
|
+
p_date: new Date(),
|
|
586
|
+
p_date_to: new Date(),
|
|
587
|
+
type_manager: "main"
|
|
588
|
+
}),
|
|
589
|
+
options: {
|
|
590
|
+
select: ["ID", "NAME"]
|
|
591
|
+
},
|
|
592
|
+
autoParse: true,
|
|
593
|
+
transform: (rows) =>
|
|
594
|
+
rows.map((row) => ({
|
|
595
|
+
id: row.ID,
|
|
596
|
+
name: row.NAME
|
|
597
|
+
}))
|
|
598
|
+
});
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### Пример агрегации в один объект
|
|
602
|
+
|
|
603
|
+
```ts
|
|
604
|
+
type Summary = {
|
|
605
|
+
total: number;
|
|
606
|
+
firstId?: string;
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
const queryFn = odataQueryFn<RawRow, Summary>({
|
|
610
|
+
odata: { service: "TEXT_DEMO_SRV", target: "TextEntity" },
|
|
611
|
+
transform: (rows) => ({
|
|
612
|
+
total: rows.length,
|
|
613
|
+
firstId: rows[0]?.ID
|
|
614
|
+
})
|
|
615
|
+
});
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
## `odataFunctionImportFn`
|
|
619
|
+
|
|
620
|
+
Используйте для вызова `FunctionImport`.
|
|
621
|
+
|
|
622
|
+
Конфигурация:
|
|
623
|
+
|
|
624
|
+
```ts
|
|
625
|
+
{
|
|
626
|
+
odata: ODataServiceConfig;
|
|
627
|
+
params: ODataParameters;
|
|
628
|
+
}
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
Поведение:
|
|
632
|
+
|
|
633
|
+
- target должен быть `FunctionImport`;
|
|
634
|
+
- HTTP-метод берётся из metadata;
|
|
635
|
+
- если `httpMethod` отсутствует, будет ошибка;
|
|
636
|
+
- `params` обязательны;
|
|
637
|
+
- `body` не используется;
|
|
638
|
+
- параметры target становятся query string-параметрами;
|
|
639
|
+
- `options` дописываются в тот же query string.
|
|
640
|
+
|
|
641
|
+
Пример:
|
|
642
|
+
|
|
643
|
+
```ts
|
|
644
|
+
const mutationFn = odataFunctionImportFn<CreateTransportRequestRaw, TransportRequest>({
|
|
645
|
+
odata: { service: "TEXT_CONFIG_SRV", target: "createTextRequest" },
|
|
646
|
+
params: wrapODataParams({
|
|
647
|
+
type: "workbench",
|
|
648
|
+
text: "Новый транспорт"
|
|
649
|
+
}),
|
|
650
|
+
transform: (raw) => mapCreatedTransportRequest(raw, "workbench")
|
|
651
|
+
});
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
URL будет вида:
|
|
655
|
+
|
|
656
|
+
```txt
|
|
657
|
+
/TEXT_CONFIG_SRV/createTextRequest?type='workbench'&text='Новый транспорт'
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
## Низкоуровневый helper `odataFetchFn`
|
|
661
|
+
|
|
662
|
+
`odataFetchFn` нужен, когда вы хотите явно указать семантику операции отдельным аргументом:
|
|
663
|
+
|
|
664
|
+
```ts
|
|
665
|
+
const queryFn = odataFetchFn("query", {
|
|
666
|
+
odata: { service: "TEXT_REPORT_SRV", target: "TEXT_REPORT_ENTITY" }
|
|
667
|
+
});
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
Обычно в прикладном коде удобнее использовать специализированные helper-ы:
|
|
671
|
+
|
|
672
|
+
- `odataQueryFn`
|
|
673
|
+
- `odataReadFn`
|
|
674
|
+
- `odataCreateFn`
|
|
675
|
+
- `odataUpdateFn`
|
|
676
|
+
- `odataDeleteFn`
|
|
677
|
+
- `odataFunctionImportFn`
|
|
678
|
+
|
|
679
|
+
## Автопарсинг и transform
|
|
680
|
+
|
|
681
|
+
Только `autoParse`:
|
|
682
|
+
|
|
683
|
+
```ts
|
|
684
|
+
const queryFn = odataQueryFn<RawRow>({
|
|
685
|
+
odata: { service: "TEXT_DEMO_SRV", target: "TextEntity" },
|
|
686
|
+
autoParse: true
|
|
687
|
+
});
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
`autoParse` вместе с `transform`:
|
|
691
|
+
|
|
692
|
+
```ts
|
|
693
|
+
const queryFn = odataQueryFn<RawRow, Row>({
|
|
694
|
+
odata: { service: "TEXT_DEMO_SRV", target: "TextEntity" },
|
|
695
|
+
autoParse: true,
|
|
696
|
+
transform: (rows) =>
|
|
697
|
+
rows.map((row) => ({
|
|
698
|
+
id: row.ID,
|
|
699
|
+
name: row.NAME
|
|
700
|
+
}))
|
|
701
|
+
});
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
Если включён `autoParse`, `transform` получает уже распарсенные данные.
|
|
705
|
+
|
|
706
|
+
## Runtime-валидация
|
|
707
|
+
|
|
708
|
+
Перед выполнением запроса helper:
|
|
709
|
+
|
|
710
|
+
- проверяет, что target существует в metadata;
|
|
711
|
+
- проверяет, что target не конфликтует одновременно как `Entity` и как `FunctionImport`;
|
|
712
|
+
- проверяет совместимость выбранной операции и target.
|
|
713
|
+
|
|
714
|
+
Ошибка будет, если:
|
|
715
|
+
|
|
716
|
+
- `odataFunctionImportFn` вызван для обычной `Entity`;
|
|
717
|
+
- `odataCreateFn`, `odataUpdateFn`, `odataDeleteFn`, `odataReadFn`, `odataQueryFn` вызваны для `FunctionImport`;
|
|
718
|
+
- `odataReadFn` вызван для query entity с `result`;
|
|
719
|
+
- metadata не содержит `httpMethod` для `FunctionImport`;
|
|
720
|
+
- отсутствует обязательный параметр target.
|
|
721
|
+
|
|
722
|
+
## Возвращаемое значение
|
|
723
|
+
|
|
724
|
+
Все helper-ы возвращают:
|
|
725
|
+
|
|
726
|
+
```ts
|
|
727
|
+
Promise<{
|
|
728
|
+
data: ...;
|
|
729
|
+
totalCount?: number;
|
|
730
|
+
}>
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
Тип `data` зависит от helper-а:
|
|
734
|
+
|
|
735
|
+
- `odataCreateFn`, `odataUpdateFn`, `odataDeleteFn`, `odataReadFn`, `odataFunctionImportFn`
|
|
736
|
+
- без `transform`: `I`
|
|
737
|
+
- с `transform`: `O`
|
|
738
|
+
- `odataQueryFn`
|
|
739
|
+
- без `transform`: `I[]`
|
|
740
|
+
- с `transform`: `O[]` или `O`
|
|
741
|
+
|
|
742
|
+
`totalCount` приходит из транспортного слоя, если backend его вернул.
|
|
743
|
+
|
|
744
|
+
## Что использовать в прикладном коде
|
|
745
|
+
|
|
746
|
+
Практическая рекомендация:
|
|
747
|
+
|
|
748
|
+
- чтение списка или query entity: `odataQueryFn`
|
|
749
|
+
- чтение одной сущности по ключу: `odataReadFn`
|
|
750
|
+
- создание: `odataCreateFn`
|
|
751
|
+
- обновление: `odataUpdateFn`
|
|
752
|
+
- удаление: `odataDeleteFn`
|
|
753
|
+
- вызов `FunctionImport`: `odataFunctionImportFn`
|
|
754
|
+
- низкоуровневый универсальный вход: `odataFetchFn`
|
|
755
|
+
|
|
756
|
+
## Связанные файлы
|
|
757
|
+
|
|
758
|
+
- `odataFetch.ts` — низкоуровневый transport helper без metadata-логики
|
|
759
|
+
- `parseODataResponseByMetadata.ts` — автопарсинг ответа по metadata
|
|
760
|
+
- `useODataMetadataQuery.ts` — загрузка metadata
|
|
761
|
+
- `wrapParams.ts` — helper для сборки `ODataParameters`
|