@ryuzaki13/react-foundation-api 1.1.17 → 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.
- 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/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/src/resource/README.mdx
CHANGED
|
@@ -25,6 +25,54 @@ import { Meta } from "@storybook/addon-docs/blocks";
|
|
|
25
25
|
|
|
26
26
|
Для стандартной модели сохранённых записей `list/latest/history/save/create/delete` удобнее [`/persisted`](../persisted/README.mdx).
|
|
27
27
|
|
|
28
|
+
Не используйте `/resource`, если нужен один приватный request в одной feature и descriptor не даёт повторного использования. Абстракция полезна, когда resource имеет устойчивое имя, несколько consumers/operations и общую cache policy.
|
|
29
|
+
|
|
30
|
+
## Ментальная модель
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
Descriptor
|
|
34
|
+
├── namespace + resource ───────────────► base query key
|
|
35
|
+
├── scope policy ───────────────────────► можно ли выполнять operation
|
|
36
|
+
├── query operations
|
|
37
|
+
│ ├── search(args) ───────────────────► Promise<SearchResult>
|
|
38
|
+
│ └── detail(args) ───────────────────► Promise<Order>
|
|
39
|
+
└── mutation operations
|
|
40
|
+
├── save(input) ────────────────────► Promise<Order>
|
|
41
|
+
└── archive(input) ─────────────────► Promise<void>
|
|
42
|
+
│
|
|
43
|
+
▼
|
|
44
|
+
cache strategy
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Descriptor не хранит данные. Данные хранятся в QueryClient по keys descriptor-а.
|
|
48
|
+
|
|
49
|
+
### Что является identity
|
|
50
|
+
|
|
51
|
+
Для query:
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
namespace + resource + normalized scope + operation name + normalized args
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Для mutation key:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
namespace + resource + normalized scope + operation name
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Mutation input в key не входит. Он передаётся `mutationFn` в момент `mutate(input)`.
|
|
64
|
+
|
|
65
|
+
### Query cache — server snapshot
|
|
66
|
+
|
|
67
|
+
Query data — копия последнего известного server state. Не редактируйте её как form draft:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// Нельзя: in-place mutation cache object.
|
|
71
|
+
query.data.title = draftTitle;
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Draft формы должен быть отдельным state. После save cache synchronizes через strategy.
|
|
75
|
+
|
|
28
76
|
## Установка и provider
|
|
29
77
|
|
|
30
78
|
```bash
|
|
@@ -137,6 +185,52 @@ const saveOperation = createResourceMutationOperation<
|
|
|
137
185
|
|
|
138
186
|
Factories `createResourceQueryOperation` и `createResourceMutationOperation` не изменяют объект. Они помогают TypeScript сохранить generic types.
|
|
139
187
|
|
|
188
|
+
Они также не добавляют retry, validation, transport headers или cache behavior. Всё перечисленное должно быть явно в operation/adapter/QueryClient policy.
|
|
189
|
+
|
|
190
|
+
### Query operation contract
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
interface ResourceQueryOperation<TScope, TArgs, TResult> {
|
|
194
|
+
execute(context: {
|
|
195
|
+
scope: TScope;
|
|
196
|
+
args: TArgs;
|
|
197
|
+
client: QueryClient;
|
|
198
|
+
signal?: AbortSignal;
|
|
199
|
+
}): Promise<TResult>;
|
|
200
|
+
isEnabled?(scope: TScope | null | undefined, args: TArgs): boolean;
|
|
201
|
+
readonly staleTime?: number;
|
|
202
|
+
readonly gcTime?: number;
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`client` позволяет operation переиспользовать другие query snapshots или metadata. Не создавайте новый QueryClient внутри execute.
|
|
207
|
+
|
|
208
|
+
`signal` нужно передавать transport-у:
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
const response = await fetch(url, { signal });
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Mutation operation contract
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
interface ResourceMutationOperation<TScope, TInput, TResult, TDescriptor> {
|
|
218
|
+
execute(context: {
|
|
219
|
+
scope: TScope;
|
|
220
|
+
input: TInput;
|
|
221
|
+
client: QueryClient;
|
|
222
|
+
}): Promise<TResult>;
|
|
223
|
+
readonly cacheStrategy?: ResourceCacheStrategy<
|
|
224
|
+
TScope,
|
|
225
|
+
TInput,
|
|
226
|
+
TResult,
|
|
227
|
+
TDescriptor
|
|
228
|
+
>;
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Generic mutation contract не содержит AbortSignal. Не обещайте automatic cancellation mutation.
|
|
233
|
+
|
|
140
234
|
### 3. Создать descriptor
|
|
141
235
|
|
|
142
236
|
```ts
|
|
@@ -159,6 +253,42 @@ const ordersResource = createResourceDescriptor({
|
|
|
159
253
|
|
|
160
254
|
`normalizeScope` влияет только на query key. В `execute` приходит исходный literal scope. Например, key получит trimmed `companyId`, но operation увидит строку ровно в переданном object. Это позволяет нормализовать identity, не переписывая draft/input.
|
|
161
255
|
|
|
256
|
+
### Поля descriptor-а
|
|
257
|
+
|
|
258
|
+
| Поле | Назначение |
|
|
259
|
+
| --- | --- |
|
|
260
|
+
| `namespace` | Верхний технический namespace keys, например `sales` |
|
|
261
|
+
| `resource` | Устойчивое имя resource, например `orders` |
|
|
262
|
+
| `operations.queries` | Record read operations с произвольными names |
|
|
263
|
+
| `operations.mutations` | Record write operations |
|
|
264
|
+
| `keys` | Custom key factory или generated factory |
|
|
265
|
+
| `normalizeScope` | Настройка generated key factory |
|
|
266
|
+
| `isEnabled` | Общая доступность resource scope |
|
|
267
|
+
| `getScopeError` | Error message для фактического execute invalid scope |
|
|
268
|
+
|
|
269
|
+
Operations можно опустить с одной стороны:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
createResourceDescriptor({
|
|
273
|
+
namespace: "catalog",
|
|
274
|
+
resource: "countries",
|
|
275
|
+
operations: {
|
|
276
|
+
queries: { list: listCountriesOperation }
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
В runtime отсутствующая группа превращается в пустой object.
|
|
282
|
+
|
|
283
|
+
### Descriptor создаётся один раз
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
// На уровне module — правильно.
|
|
287
|
+
export const ordersResource = createResourceDescriptor(...);
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Не создавайте его внутри component render: descriptor и callbacks должны иметь устойчивую identity, а module-level definition проще тестировать и переиспользовать.
|
|
291
|
+
|
|
162
292
|
### 4. Использовать query hook
|
|
163
293
|
|
|
164
294
|
```tsx
|
|
@@ -198,6 +328,14 @@ mutation.mutate({ id: "42", title: "Новый заголовок" });
|
|
|
198
328
|
|
|
199
329
|
Mutation key содержит resource/scope/operation, но не input. Input передаётся только в `mutationFn`.
|
|
200
330
|
|
|
331
|
+
### Несколько одновременных mutations
|
|
332
|
+
|
|
333
|
+
Две `save` mutations одного scope имеют одинаковый mutation key, но разные inputs и отдельные mutation instances. Mutation key группирует operations для наблюдения/diagnostics; это не Query cache slot с data.
|
|
334
|
+
|
|
335
|
+
### Scope проверяется при `mutate`
|
|
336
|
+
|
|
337
|
+
Hook можно создать с nullable scope, но actual mutation вызовет `assertResourceScope`. Если scope invalid, server execute не начнётся и mutation получит error.
|
|
338
|
+
|
|
201
339
|
## Query lifecycle
|
|
202
340
|
|
|
203
341
|
`buildResourceQueryOptions(descriptor, name, scope, args)` создаёт TanStack `queryOptions`:
|
|
@@ -220,6 +358,54 @@ const options = buildResourceQueryOptions(
|
|
|
220
358
|
|
|
221
359
|
Перед фактическим `execute` scope обязан быть не `null`/`undefined` и пройти descriptor policy. Иначе выбрасывается `getScopeError` или стандартная ошибка.
|
|
222
360
|
|
|
361
|
+
### Критическое правило nullable scope
|
|
362
|
+
|
|
363
|
+
Если scope может быть `null`/`undefined`, descriptor обязан иметь `isEnabled`:
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
isEnabled: (scope) => Boolean(scope?.companyId)
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Без него default enabled равен `true`, даже для `undefined`. TanStack Query попытается запустить query, а query function выбросит scope error.
|
|
370
|
+
|
|
371
|
+
Operation-level `isEnabled` не заменяет общую scope policy, хотя тоже может проверить scope. Хорошая структура:
|
|
372
|
+
|
|
373
|
+
```ts
|
|
374
|
+
isEnabled: (scope) => Boolean(scope?.companyId),
|
|
375
|
+
|
|
376
|
+
// В operation:
|
|
377
|
+
isEnabled: (_scope, args) => args.limit > 0 && args.search.length >= 2
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Стандартная scope error
|
|
381
|
+
|
|
382
|
+
Если custom `getScopeError` отсутствует:
|
|
383
|
+
|
|
384
|
+
```text
|
|
385
|
+
Недостаточно данных scope для ресурса 'orders'.
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
`getScopeError` вызывается только при попытке actual execution invalid scope. Возвращаемое `null` приводит к standard fallback из-за `??`.
|
|
389
|
+
|
|
390
|
+
### Отсутствующая operation
|
|
391
|
+
|
|
392
|
+
При неверном runtime descriptor-е error возникает до transport:
|
|
393
|
+
|
|
394
|
+
```text
|
|
395
|
+
Ресурс 'orders' не поддерживает read-операцию 'detail'.
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Runtime guard проверяет object и callable `execute`. TypeScript обычно ловит неверное name раньше, если descriptor types сохранены.
|
|
399
|
+
|
|
400
|
+
### `staleTime` и `gcTime`
|
|
401
|
+
|
|
402
|
+
Значения просто передаются TanStack Query:
|
|
403
|
+
|
|
404
|
+
- `staleTime` не является HTTP TTL;
|
|
405
|
+
- `gcTime` отсчитывается для неактивного query;
|
|
406
|
+
- отсутствие значения означает default QueryClient policy;
|
|
407
|
+
- они не влияют на mutation.
|
|
408
|
+
|
|
223
409
|
## Императивная загрузка
|
|
224
410
|
|
|
225
411
|
```ts
|
|
@@ -234,6 +420,28 @@ const orders = await getResourceQueryData(
|
|
|
234
420
|
|
|
235
421
|
Функция использует `queryClient.fetchQuery`, поэтому переиспользует query cache. Передавайте только валидный scope: imperative `fetchQuery` нельзя воспринимать как disabled React observer; query function всё равно защищает scope и может бросить ошибку.
|
|
236
422
|
|
|
423
|
+
### Loader/preload
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
export async function loadOrdersRoute({
|
|
427
|
+
companyId,
|
|
428
|
+
queryClient
|
|
429
|
+
}: {
|
|
430
|
+
companyId: string;
|
|
431
|
+
queryClient: QueryClient;
|
|
432
|
+
}) {
|
|
433
|
+
return getResourceQueryData(
|
|
434
|
+
ordersResource,
|
|
435
|
+
"search",
|
|
436
|
+
{ companyId },
|
|
437
|
+
{ search: "", limit: 50 },
|
|
438
|
+
queryClient
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Component с теми же exact inputs получит тот же query key и переиспользует snapshot.
|
|
444
|
+
|
|
237
445
|
## Query keys
|
|
238
446
|
|
|
239
447
|
```ts
|
|
@@ -280,6 +488,57 @@ normalizeResourceKeyValue({ b: " x ", a: undefined });
|
|
|
280
488
|
|
|
281
489
|
Нормализуйте специальные значения сами, например Date → ISO string. Query key не должен содержать secrets, DOM nodes или огромные payload.
|
|
282
490
|
|
|
491
|
+
### Дополнительные примеры normalization
|
|
492
|
+
|
|
493
|
+
```ts
|
|
494
|
+
normalizeResourceKeyValue(undefined);
|
|
495
|
+
// null
|
|
496
|
+
|
|
497
|
+
normalizeResourceKeyValue([" a ", undefined, false]);
|
|
498
|
+
// ["a", null, false]
|
|
499
|
+
|
|
500
|
+
normalizeResourceKeyValue({ z: 1, a: { b: " x " } });
|
|
501
|
+
// { a: { b: "x" }, z: 1 }
|
|
502
|
+
|
|
503
|
+
normalizeResourceKeyValue(new Date("2026-08-21T00:00:00Z"));
|
|
504
|
+
// {} — Date не поддерживается специально
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Object key order
|
|
508
|
+
|
|
509
|
+
Эти scopes создадут одинаковую normalized identity:
|
|
510
|
+
|
|
511
|
+
```ts
|
|
512
|
+
{ companyId: "1000", plant: "1100" }
|
|
513
|
+
{ plant: "1100", companyId: "1000" }
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
Object keys сортируются рекурсивно. Array order при этом значим:
|
|
517
|
+
|
|
518
|
+
```ts
|
|
519
|
+
["1000", "2000"] !== ["2000", "1000"]
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
Если array представляет set, отсортируйте его в `normalizeScope`/до args.
|
|
523
|
+
|
|
524
|
+
### Custom keys
|
|
525
|
+
|
|
526
|
+
Можно передать полностью свою `ResourceKeys`, например для совместимости с существующей cache taxonomy. Factory обязана сохранять prefix semantics:
|
|
527
|
+
|
|
528
|
+
```ts
|
|
529
|
+
const keys = createResourceKeys<OrdersScope, "search" | "save">({
|
|
530
|
+
namespace: "sales-v2",
|
|
531
|
+
resource: "orders"
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
createResourceDescriptor({
|
|
535
|
+
// ...
|
|
536
|
+
keys
|
|
537
|
+
});
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
Не собирайте key ad hoc в cache strategy: используйте `descriptor.keys`.
|
|
541
|
+
|
|
283
542
|
## Cache strategies после mutation
|
|
284
543
|
|
|
285
544
|
### Инвалидация scope
|
|
@@ -336,6 +595,183 @@ useResourceMutation(descriptor, "save", scope, {
|
|
|
336
595
|
|
|
337
596
|
`applyResourceCacheStrategy(strategy, context)` — низкоуровневый helper для ручного применения optional strategy.
|
|
338
597
|
|
|
598
|
+
### Полный cache context
|
|
599
|
+
|
|
600
|
+
```ts
|
|
601
|
+
interface ResourceCacheStrategyContext<TScope, TInput, TResult, TDescriptor> {
|
|
602
|
+
readonly client: QueryClient;
|
|
603
|
+
readonly descriptor: TDescriptor;
|
|
604
|
+
readonly scope: TScope;
|
|
605
|
+
readonly input: TInput;
|
|
606
|
+
readonly result: TResult;
|
|
607
|
+
}
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
Strategy имеет все данные для точного key/update, но не должна содержать domain network side effects. Server write уже завершён.
|
|
611
|
+
|
|
612
|
+
### Порядок mutation success
|
|
613
|
+
|
|
614
|
+
```text
|
|
615
|
+
operation.execute success
|
|
616
|
+
│
|
|
617
|
+
▼
|
|
618
|
+
resolved scope повторно проверяется
|
|
619
|
+
│
|
|
620
|
+
▼
|
|
621
|
+
selected cache strategy
|
|
622
|
+
│ await
|
|
623
|
+
▼
|
|
624
|
+
hook options.onSuccess(result, input)
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
`options.cacheStrategy` выбирается так:
|
|
628
|
+
|
|
629
|
+
| Значение | Поведение |
|
|
630
|
+
| --- | --- |
|
|
631
|
+
| `undefined` | Использовать strategy operation |
|
|
632
|
+
| `null` | Не выполнять strategy |
|
|
633
|
+
| object | Полностью заменить operation strategy |
|
|
634
|
+
|
|
635
|
+
### Invalidate или set data
|
|
636
|
+
|
|
637
|
+
Используйте invalidation, если server:
|
|
638
|
+
|
|
639
|
+
- вычисляет дополнительные fields;
|
|
640
|
+
- меняет сортировку/агрегации;
|
|
641
|
+
- write затрагивает несколько неизвестных queries.
|
|
642
|
+
|
|
643
|
+
Используйте `setQueryData`, если response содержит authoritative готовую запись и точный key известен.
|
|
644
|
+
|
|
645
|
+
Композиция полезна, когда detail можно обновить сразу, а lists безопаснее refetch-нуть.
|
|
646
|
+
|
|
647
|
+
### Current data может быть `undefined`
|
|
648
|
+
|
|
649
|
+
Updater обязан это учесть:
|
|
650
|
+
|
|
651
|
+
```ts
|
|
652
|
+
update: (current, { result }) => {
|
|
653
|
+
if (!current) return [result];
|
|
654
|
+
return current.map((item) => item.id === result.id ? result : item);
|
|
655
|
+
}
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
Возвращайте новое значение. Не делайте `current.push(...)`.
|
|
659
|
+
|
|
660
|
+
### Ошибка cache strategy после server success
|
|
661
|
+
|
|
662
|
+
Server mutation уже могла примениться. Если strategy бросила error:
|
|
663
|
+
|
|
664
|
+
- следующие composed strategies не выполняются;
|
|
665
|
+
- hook custom `onSuccess` не вызывается;
|
|
666
|
+
- caller может получить rejected async lifecycle.
|
|
667
|
+
|
|
668
|
+
Не повторяйте write автоматически только из-за cache error: это может создать duplicate. Разделяйте retry server operation и cache recovery.
|
|
669
|
+
|
|
670
|
+
## Transport adapters
|
|
671
|
+
|
|
672
|
+
Generic operation может использовать:
|
|
673
|
+
|
|
674
|
+
- `/http` для обычного REST;
|
|
675
|
+
- `/odata` для metadata-aware SAP operation;
|
|
676
|
+
- `/server-fn` для функций `{ data }`;
|
|
677
|
+
- тестовый in-memory executor.
|
|
678
|
+
|
|
679
|
+
Resource не должен знать бизнес transport автоматически. Это позволяет одному descriptor pattern работать с разными infrastructures без смешивания contracts.
|
|
680
|
+
|
|
681
|
+
## Тестирование
|
|
682
|
+
|
|
683
|
+
### Key factory
|
|
684
|
+
|
|
685
|
+
```ts
|
|
686
|
+
it("нормализует scope и args", () => {
|
|
687
|
+
const key = ordersResource.keys.operation(
|
|
688
|
+
"search",
|
|
689
|
+
{ companyId: " 1000 " },
|
|
690
|
+
{ limit: 50, search: " test " }
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
expect(key).toEqual([
|
|
694
|
+
"sales",
|
|
695
|
+
"orders",
|
|
696
|
+
{ companyId: "1000" },
|
|
697
|
+
"search",
|
|
698
|
+
{ limit: 50, search: "test" }
|
|
699
|
+
]);
|
|
700
|
+
});
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### Query operation context
|
|
704
|
+
|
|
705
|
+
```ts
|
|
706
|
+
it("передаёт scope, args, client и signal", async () => {
|
|
707
|
+
const execute = vi.fn().mockResolvedValue([]);
|
|
708
|
+
const operation = createResourceQueryOperation({ execute });
|
|
709
|
+
const descriptor = createResourceDescriptor({
|
|
710
|
+
namespace: "test",
|
|
711
|
+
resource: "orders",
|
|
712
|
+
operations: { queries: { list: operation } },
|
|
713
|
+
isEnabled: (scope) => Boolean(scope)
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
await queryClient.fetchQuery(
|
|
717
|
+
buildResourceQueryOptions(descriptor, "list", "scope-1", {})
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
expect(execute).toHaveBeenCalledWith(
|
|
721
|
+
expect.objectContaining({
|
|
722
|
+
scope: "scope-1",
|
|
723
|
+
args: {},
|
|
724
|
+
client: queryClient,
|
|
725
|
+
signal: expect.any(AbortSignal)
|
|
726
|
+
})
|
|
727
|
+
);
|
|
728
|
+
});
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
Покройте:
|
|
732
|
+
|
|
733
|
+
- nullable/invalid scope disabled;
|
|
734
|
+
- custom scope error при imperative execution;
|
|
735
|
+
- descriptor + operation enabled policies;
|
|
736
|
+
- missing operation runtime error;
|
|
737
|
+
- same normalized inputs → same key;
|
|
738
|
+
- meaningful inputs → different keys;
|
|
739
|
+
- signal передаётся query transport;
|
|
740
|
+
- mutation success strategy order;
|
|
741
|
+
- `undefined`/`null`/override strategy semantics;
|
|
742
|
+
- setQueryData не мутирует old snapshot;
|
|
743
|
+
- composed strategy прекращается на error.
|
|
744
|
+
|
|
745
|
+
## Частые ошибки
|
|
746
|
+
|
|
747
|
+
### Descriptor без `isEnabled` при nullable scope
|
|
748
|
+
|
|
749
|
+
Query будет enabled и затем упадёт на scope assertion.
|
|
750
|
+
|
|
751
|
+
### Date прямо в args
|
|
752
|
+
|
|
753
|
+
Default normalizer превратит Date в `{}`. Передавайте ISO string.
|
|
754
|
+
|
|
755
|
+
### Нормализовать scope и ожидать изменённый execute input
|
|
756
|
+
|
|
757
|
+
`normalizeScope` меняет только key identity.
|
|
758
|
+
|
|
759
|
+
### Не включить args, влияющие на response
|
|
760
|
+
|
|
761
|
+
Factory включает переданный args целиком. Не прячьте server filter во внешнем closure, отсутствующем в args/key.
|
|
762
|
+
|
|
763
|
+
### Создавать descriptor в render
|
|
764
|
+
|
|
765
|
+
Держите его на module boundary.
|
|
766
|
+
|
|
767
|
+
### Повторить mutation после cache error
|
|
768
|
+
|
|
769
|
+
Server write уже мог завершиться. Восстановите cache отдельно.
|
|
770
|
+
|
|
771
|
+
### Deep import operation type
|
|
772
|
+
|
|
773
|
+
Используйте public subpath `/resource`.
|
|
774
|
+
|
|
339
775
|
## Ошибки и границы
|
|
340
776
|
|
|
341
777
|
- Отсутствующая operation даёт раннюю ошибку с именем resource/operation.
|
|
@@ -346,6 +782,32 @@ useResourceMutation(descriptor, "save", scope, {
|
|
|
346
782
|
- Не используйте query cache как authoritative persistent storage.
|
|
347
783
|
- Cache strategy выполняется только после успешной mutation.
|
|
348
784
|
|
|
785
|
+
## FAQ
|
|
786
|
+
|
|
787
|
+
### Resource сам выполняет HTTP?
|
|
788
|
+
|
|
789
|
+
Нет. Transport находится в `operation.execute` или adapter factory.
|
|
790
|
+
|
|
791
|
+
### Может ли scope быть primitive?
|
|
792
|
+
|
|
793
|
+
Да: string/number и другие типы разрешены generic-ом. Он должен устойчиво описывать tenant/user/entity boundary.
|
|
794
|
+
|
|
795
|
+
### Почему args входят в query key автоматически?
|
|
796
|
+
|
|
797
|
+
Потому что разные args обычно означают разные server snapshots.
|
|
798
|
+
|
|
799
|
+
### Почему mutation input не входит в mutation key?
|
|
800
|
+
|
|
801
|
+
Mutation key описывает семейство operation, а конкретный input хранится в mutation instance execution state.
|
|
802
|
+
|
|
803
|
+
### Как отключить operation cache strategy для одного hook?
|
|
804
|
+
|
|
805
|
+
Передать `cacheStrategy: null`.
|
|
806
|
+
|
|
807
|
+
### Можно ли использовать без React hook?
|
|
808
|
+
|
|
809
|
+
Queries — через `getResourceQueryData`/`buildResourceQueryOptions`. Для mutations public convenience API сейчас hook-oriented; operation можно orchestrate на корректной owner boundary.
|
|
810
|
+
|
|
349
811
|
## Полный API
|
|
350
812
|
|
|
351
813
|
| Группа | Exports |
|