@ryuzaki13/react-foundation-lib 2.19.0 → 2.21.0
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 +2 -0
- package/dist/analytical-table/aggregation.d.ts +4 -0
- package/dist/analytical-table/aggregation.d.ts.map +1 -0
- package/dist/analytical-table/buildAnalyticalTableModel.d.ts +4 -0
- package/dist/analytical-table/buildAnalyticalTableModel.d.ts.map +1 -0
- package/dist/analytical-table/calculationTypes.d.ts +11 -0
- package/dist/analytical-table/calculationTypes.d.ts.map +1 -0
- package/dist/analytical-table/calculations.d.ts +6 -0
- package/dist/analytical-table/calculations.d.ts.map +1 -0
- package/dist/analytical-table/columnTypes.d.ts +30 -0
- package/dist/analytical-table/columnTypes.d.ts.map +1 -0
- package/dist/analytical-table/dataTypes.d.ts +14 -0
- package/dist/analytical-table/dataTypes.d.ts.map +1 -0
- package/dist/analytical-table/dateColumnTypes.d.ts +31 -0
- package/dist/analytical-table/dateColumnTypes.d.ts.map +1 -0
- package/dist/analytical-table/dateColumns.d.ts +11 -0
- package/dist/analytical-table/dateColumns.d.ts.map +1 -0
- package/dist/analytical-table/expansion.d.ts +4 -0
- package/dist/analytical-table/expansion.d.ts.map +1 -0
- package/dist/analytical-table/exportProjection.d.ts +4 -0
- package/dist/analytical-table/exportProjection.d.ts.map +1 -0
- package/dist/analytical-table/exportTypes.d.ts +31 -0
- package/dist/analytical-table/exportTypes.d.ts.map +1 -0
- package/dist/analytical-table/filtering.d.ts +4 -0
- package/dist/analytical-table/filtering.d.ts.map +1 -0
- package/dist/analytical-table/index.d.ts +8 -0
- package/dist/analytical-table/index.d.ts.map +1 -0
- package/dist/analytical-table/index.js +364 -0
- package/dist/analytical-table/index.js.map +1 -0
- package/dist/analytical-table/indexAnalyticalRows.d.ts +4 -0
- package/dist/analytical-table/indexAnalyticalRows.d.ts.map +1 -0
- package/dist/analytical-table/modelTypes.d.ts +43 -0
- package/dist/analytical-table/modelTypes.d.ts.map +1 -0
- package/dist/analytical-table/types.d.ts +8 -0
- package/dist/analytical-table/types.d.ts.map +1 -0
- package/dist/analytical-table/valueComparison.d.ts +5 -0
- package/dist/analytical-table/valueComparison.d.ts.map +1 -0
- package/dist/analytical-table/viewTypes.d.ts +24 -0
- package/dist/analytical-table/viewTypes.d.ts.map +1 -0
- package/dist/chunks/{formatters-hHUJpZqD.js → formatters-CEltML3y.js} +224 -224
- package/dist/chunks/{formatters-hHUJpZqD.js.map → formatters-CEltML3y.js.map} +1 -1
- package/dist/chunks/{validate-DnkaY38p.js → validate-M2zPiLXn.js} +24 -24
- package/dist/chunks/{validate-DnkaY38p.js.map → validate-M2zPiLXn.js.map} +1 -1
- package/dist/error-report/diagnostics.d.ts.map +1 -1
- package/dist/error-report/drafts.d.ts.map +1 -1
- package/dist/error-report/errorReportStorage.d.ts +18 -0
- package/dist/error-report/errorReportStorage.d.ts.map +1 -0
- package/dist/error-report/index.d.ts +1 -0
- package/dist/error-report/index.d.ts.map +1 -1
- package/dist/error-report/index.js +557 -540
- package/dist/error-report/index.js.map +1 -1
- package/dist/excel/index.js +36 -36
- package/dist/excel/index.js.map +1 -1
- package/dist/formatters/index.js +2 -2
- package/dist/hooks/useFloatingListbox.d.ts +1 -1
- package/dist/odata-service/formatters.d.ts +2 -2
- package/package.json +5 -1
- package/src/analytical-table/README.mdx +169 -0
- package/src/error-report/README.mdx +44 -5
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Analytical Table: чистая модель полного снимка
|
|
2
|
+
|
|
3
|
+
Модуль `@ryuzaki13/react-foundation-lib/analytical-table` предоставляет вычислительный
|
|
4
|
+
слой аналитической таблицы. Он подходит для SSR и браузера, не использует React hooks,
|
|
5
|
+
QueryClient, serverFn, OData-запросы, SAP metadata, DOM или browser storage. Загрузкой, ошибками,
|
|
6
|
+
отменой и авторизацией владеет транспорт приложения. Результат запроса передаётся
|
|
7
|
+
как неизменяемый `AnalyticalSnapshot`; дополнительных запросов при группировке,
|
|
8
|
+
сортировке или раскрытии нет.
|
|
9
|
+
|
|
10
|
+
## Контракт и владельцы состояния
|
|
11
|
+
|
|
12
|
+
`AnalyticalSnapshot<T>` содержит `rows`. Каждая `AnalyticalDataRow<T>` содержит
|
|
13
|
+
стабильный `id`, необязательный `parentId`, значения `values`, исходную запись
|
|
14
|
+
`original` и необязательный предметный `kind`. Нельзя использовать номер позиции
|
|
15
|
+
строки как идентификатор: сортировка и обновление снимка должны сохранять выбор и
|
|
16
|
+
раскрытие. Runtime не изменяет исходные строки и не копирует предметные DTO
|
|
17
|
+
рекурсивно: `original` остаётся ссылкой на неизменяемую запись потребителя.
|
|
18
|
+
|
|
19
|
+
`AnalyticalColumn<T>` задаёт идентификатор, подпись, тип значения, необязательные
|
|
20
|
+
сравнение, фильтр, агрегацию и вычисление. В столбцах должны присутствовать также
|
|
21
|
+
скрытые зависимости формул. Ключи и отображаемые поля группировки могут ссылаться
|
|
22
|
+
прямо на поля values, не создавая технических столбцов интерфейса. Видимость, ширина,
|
|
23
|
+
закрепление и порядок столбцов относятся к UI и не должны пересчитывать модель.
|
|
24
|
+
Форматирование значений выполняется существующим `/formatters` на границе UI или
|
|
25
|
+
экспорта; runtime сохраняет числа числами.
|
|
26
|
+
|
|
27
|
+
`AnalyticalViewState` содержит группировки, сортировку, фильтры и идентификаторы
|
|
28
|
+
раскрытых строк. Оно сериализуемо, но не является загружаемыми данными.
|
|
29
|
+
`AnalyticalTableModel` содержит дерево `rows`, все строки `flatRows`, видимые строки
|
|
30
|
+
`visibleRows`, индекс `rowById`, общие итоги `grandTotals` и диагностику иерархии.
|
|
31
|
+
`Map` результата вычисляется локально: передавать его через serverFn не требуется.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import {
|
|
35
|
+
buildAnalyticalTableModel,
|
|
36
|
+
projectAnalyticalTableExpansion,
|
|
37
|
+
type AnalyticalSnapshot
|
|
38
|
+
} from "@ryuzaki13/react-foundation-lib/analytical-table";
|
|
39
|
+
|
|
40
|
+
const snapshot: AnalyticalSnapshot = {
|
|
41
|
+
rows: [
|
|
42
|
+
{ id: "order:1", values: { customer: "Компания А", amount: 100 } },
|
|
43
|
+
{ id: "line:1", parentId: "order:1", values: { amount: 100 } }
|
|
44
|
+
]
|
|
45
|
+
};
|
|
46
|
+
const model = buildAnalyticalTableModel({
|
|
47
|
+
snapshot,
|
|
48
|
+
columns: [
|
|
49
|
+
{ id: "customer", kind: "dimension" },
|
|
50
|
+
{ id: "amount", kind: "measure", aggregate: "sum" }
|
|
51
|
+
],
|
|
52
|
+
state: {
|
|
53
|
+
grouping: [{ id: "customer", keyColumnIds: ["customer"] }],
|
|
54
|
+
sorting: [{ id: "amount", desc: true }]
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
const expanded = projectAnalyticalTableExpansion(model, "all");
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Иерархия и группировки
|
|
61
|
+
|
|
62
|
+
Построение исходной иерархии использует `/tree-table`. Повторяющиеся идентификаторы,
|
|
63
|
+
сироты и циклы диагностируются по правилам этого модуля. Идентификатор строки не
|
|
64
|
+
может быть пустым. Дополнительная группировка объединяет только корневые ветви:
|
|
65
|
+
исходные дети остаются под своими родителями. Это позволяет сохранять обязательный
|
|
66
|
+
предметный уровень и одновременно группировать его по нескольким признакам.
|
|
67
|
+
|
|
68
|
+
`AnalyticalGroupingLevel` задаёт `id`, составной `keyColumnIds` и отдельные
|
|
69
|
+
`displayColumnIds`. Идентификатор группы зависит от всего пути ключей и типов
|
|
70
|
+
значений, а не от подписи или порядка строк. Число, строка, null и undefined
|
|
71
|
+
различаются. Отображаемые поля берутся из первой строки группы: потребитель обязан
|
|
72
|
+
обеспечить, что они однозначно определяются ключом. Уровень не может иметь пустой
|
|
73
|
+
список ключей. Повторяющиеся уровни отклоняются; сортировка и фильтры требуют
|
|
74
|
+
известных дескрипторов столбцов.
|
|
75
|
+
|
|
76
|
+
Сортировка применяется между соседними узлами на каждом уровне; она не отделяет
|
|
77
|
+
детей от родителей. При равенстве сохраняется исходный порядок. Строковое сравнение
|
|
78
|
+
использует `/string-comparison`, календарные значения — `/formatters`. Для иной
|
|
79
|
+
семантики столбец задаёт `compare`.
|
|
80
|
+
|
|
81
|
+
Фильтры поддерживают equals, notEquals, contains, startsWith, in, between, gt, gte,
|
|
82
|
+
lt, lte, empty и notEmpty; текстовый поиск не зависит от регистра. Условия разных
|
|
83
|
+
столбцов объединяются через AND. Фильтрация работает над исходными вычисленными
|
|
84
|
+
строками **до** дополнительных группировок: совпавший родитель сохраняет ветвь
|
|
85
|
+
целиком, совпавший ребёнок сохраняет путь к нему. Для предметной семантики есть
|
|
86
|
+
`column.filter`.
|
|
87
|
+
|
|
88
|
+
## Итоги и зернистость
|
|
89
|
+
|
|
90
|
+
По умолчанию агрегируются только исходные корневые строки. Дочерняя детализация не
|
|
91
|
+
удваивает их меры. `isAggregateRow` позволяет явно выбрать другую зернистость.
|
|
92
|
+
`column.aggregate` поддерживает sum, min, max, avg, count и callback. Числовые
|
|
93
|
+
агрегации принимают только конечные числа; строковое преобразование внешних DTO
|
|
94
|
+
выполняется в адаптере источника. Пустое числовое множество возвращает null;
|
|
95
|
+
count считает непустые значения и возвращает 0 для пустого множества.
|
|
96
|
+
|
|
97
|
+
`aggregate(context)` возвращает значения групповых/общих итогов и может переопределить
|
|
98
|
+
результаты стандартных агрегаций. Контекст содержит `scope`, `groupingPath` и только
|
|
99
|
+
исходные строки выбранной зернистости. Например, доля должна вычисляться как
|
|
100
|
+
отношение сумм числителя и знаменателя, если предметный учёт требует взвешенного
|
|
101
|
+
результата. Библиотека не усредняет проценты вместо этого правила.
|
|
102
|
+
|
|
103
|
+
`grandTotalsScope: "filtered"` (по умолчанию) учитывает оставшиеся ветви, `"all"`
|
|
104
|
+
сохраняет общие итоги полного снимка при визуальной фильтрации. `grandTotals: false`
|
|
105
|
+
отключает их вычисление. Раскрытие и выбор строк общие итоги не меняют.
|
|
106
|
+
|
|
107
|
+
## Формулы и зависимые вычисления
|
|
108
|
+
|
|
109
|
+
`createAnalyticalCalculationPlan(columns, requiredColumnIds?)` проверяет уникальность
|
|
110
|
+
столбцов, зависимости и циклы, затем строит топологический порядок. `formula`
|
|
111
|
+
содержит `formulaId` и упорядоченные `dependencies`: исполнение использует уже
|
|
112
|
+
существующий реестр `/formulas`, который настраивает composition root приложения.
|
|
113
|
+
`calculate` содержит зависимости и чистый `compute(values)` для вычислений,
|
|
114
|
+
не представленных реестром. Одновременно задать formula и calculate нельзя.
|
|
115
|
+
|
|
116
|
+
`calculateAnalyticalValues` возвращает отдельные значения. План компилируется один
|
|
117
|
+
раз при построении модели и применяется к данным, затем к рассчитанным итогам.
|
|
118
|
+
Явная агрегация колонки, результат `aggregate(context)` и значения пути группировки
|
|
119
|
+
имеют приоритет над построчной формулой этой колонки. Зависимые формулы читают
|
|
120
|
+
готовые итоги: сумма произведений сохраняется, а отношение сумм вычисляется заново.
|
|
121
|
+
Вычисление должно учитывать отсутствующие значения в итоговой строке. Ошибки
|
|
122
|
+
конфигурации и выполнения формул не скрываются успешным нулём.
|
|
123
|
+
|
|
124
|
+
## Динамические календарные столбцы
|
|
125
|
+
|
|
126
|
+
`createAnalyticalDateColumnsPlan` строит day/week/month/quarter/year buckets с
|
|
127
|
+
включительными `from`/`to`; неделя начинается в понедельник. Идентификатор содержит
|
|
128
|
+
гранулярность и календарное начало bucket, а крайние интервалы обрезаются диапазоном.
|
|
129
|
+
По умолчанию допустимо не более 366 столбцов; превышение лимита приводит к ошибке.
|
|
130
|
+
|
|
131
|
+
Границы диапазона и поле даты каждого факта имеют контракт **YYYY-MM-DD**.
|
|
132
|
+
`Date`, числа и ISO timestamp с временем не принимаются как календарные факты.
|
|
133
|
+
Их приведение с явной timezone-политикой выполняет адаптер до передачи в runtime.
|
|
134
|
+
Неверные границы — ошибка; неверные факты и факты вне диапазона пропускаются.
|
|
135
|
+
|
|
136
|
+
`projectAnalyticalDateFacts({ rows, facts, plan })` сопоставляет факты по составному
|
|
137
|
+
`identityColumnIds`, добавляя ячейки к копиям значений. Не меняются количество,
|
|
138
|
+
порядок и идентичность исходных строк. Факты с отсутствующей частью ключа
|
|
139
|
+
пропускаются. Без фактов ячейка равна null; существующее одноимённое поле не
|
|
140
|
+
перезаписывается молча. Это отдельная проекция уже загруженных данных, не схема
|
|
141
|
+
дополнительной загрузки.
|
|
142
|
+
|
|
143
|
+
`aggregateAnalyticalDateFacts` считает общие значения по исходным фактам. Особенно
|
|
144
|
+
важно использовать его для avg: среднее от средних разных строк обычно неверно.
|
|
145
|
+
Групповые итоги для таких данных следует считать тем же способом по соответствующему
|
|
146
|
+
набору исходных фактов через aggregate callback. Факты с неполным ключом исключаются
|
|
147
|
+
и из строк, и из итогов. Область переданного набора фактов определяет потребитель.
|
|
148
|
+
|
|
149
|
+
## Экспорт и производительность
|
|
150
|
+
|
|
151
|
+
`projectAnalyticalTableExport` принимает модель, упорядоченные столбцы с необязательным
|
|
152
|
+
`format(value, row)`, область all/visible/selected, `includeGroups` и
|
|
153
|
+
`includeGrandTotals`. Результат содержит заголовки и массивы значений; дальнейшее
|
|
154
|
+
создание XLSX или документа принадлежит `/excel` или `/table-document`. По умолчанию
|
|
155
|
+
экспортируются все строки отфильтрованной модели, включая свёрнутые ветви.
|
|
156
|
+
Предметные строки-родители не считаются синтетическими группами. Общие итоги
|
|
157
|
+
форматируются отдельным `formatTotal(value)`: у них нет исходной строки `original`.
|
|
158
|
+
Без него итоги сохраняют исходные значения, даже если форматтер строк задан отдельно.
|
|
159
|
+
|
|
160
|
+
Для React следует мемоизировать базовую модель по `snapshot.rows`, колонкам,
|
|
161
|
+
группировкам, сортировке, фильтрам и правилам итогов. Время последнего запроса не
|
|
162
|
+
является зависимостью, когда данные структурно не изменились. Затем отдельно
|
|
163
|
+
вызывается `projectAnalyticalTableExpansion(model, expandedRowIds)`; раскрытие не
|
|
164
|
+
повторяет формулы, группировки и агрегации. Selection и layout остаются в UI.
|
|
165
|
+
|
|
166
|
+
Полный снимок не требует LRU страниц, sparse placeholders, remote paging или
|
|
167
|
+
второго кэша персональных данных. Если отдельный источник позднее потребует
|
|
168
|
+
постраничную серверную агрегацию, она оформляется отдельной транспортной
|
|
169
|
+
возможностью без изменения предметных правил существующего снимка.
|
|
@@ -4,7 +4,7 @@ import { Meta } from "@storybook/addon-docs/blocks";
|
|
|
4
4
|
|
|
5
5
|
# Error Report
|
|
6
6
|
|
|
7
|
-
Модуль `@ryuzaki13/react-foundation-lib/error-report` собирает клиентскую диагностику ошибки в versioned bounded draft, временно хранит последние drafts в `sessionStorage` и предоставляет durable IndexedDB queue для отложенной доставки.
|
|
7
|
+
Модуль `@ryuzaki13/react-foundation-lib/error-report` собирает клиентскую диагностику ошибки в versioned bounded draft, по умолчанию временно хранит последние drafts в `sessionStorage` и предоставляет durable IndexedDB queue для отложенной доставки. Browser bootstrap может односторонне перевести capture/drafts в память документа.
|
|
8
8
|
|
|
9
9
|
Он умеет объединить:
|
|
10
10
|
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
captureRuntimeErrorReport,
|
|
37
37
|
createErrorReportQueue,
|
|
38
38
|
createErrorInfo,
|
|
39
|
+
disableErrorReportBrowserStorage,
|
|
39
40
|
installErrorReportBrowserBreadcrumbs,
|
|
40
41
|
setErrorReportRuntimeErrorReporter,
|
|
41
42
|
setErrorReportCaptureOptions,
|
|
@@ -76,6 +77,44 @@ isErrorReportingEnabled({ isDev: false });
|
|
|
76
77
|
- в development capture-функции возвращают `undefined`;
|
|
77
78
|
- тест может явно передать `{ isDev: false }` только в эту функцию, но остальные capture-функции всё равно используют общий `__DEV__`.
|
|
78
79
|
|
|
80
|
+
## Документ без browser persistence
|
|
81
|
+
|
|
82
|
+
### `disableErrorReportBrowserStorage(): void`
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// Browser bootstrap: до первого capture и запуска UI/error-report lifecycle.
|
|
86
|
+
disableErrorReportBrowserStorage();
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Вызов односторонне переводит **capture, drafts и persisted-query diagnostics**
|
|
90
|
+
текущего документа в память. Повторный вызов ничего не сбрасывает. Обратного
|
|
91
|
+
включения нет: документ с другим режимом приложение открывает заново.
|
|
92
|
+
`setErrorReportCaptureOptions`, регистрация reporter и их cleanup не меняют
|
|
93
|
+
эту политику. В SSR вызов ничего не делает и не переключает общий process state.
|
|
94
|
+
|
|
95
|
+
Сам импорт `/error-report` не читает browser storage. В обычном режиме первый
|
|
96
|
+
`getErrorReportDrafts`, `getErrorReportDraft`, `updateErrorReportDraft` или capture
|
|
97
|
+
лениво загружает прежние drafts; дальнейшие чтение и сохранение остаются прежними.
|
|
98
|
+
В memory-режиме последние 10 drafts и отдельный стабильный session ID живут
|
|
99
|
+
только в памяти документа. Уже загруженные обычные drafts не копируются туда,
|
|
100
|
+
а существующие записи в browser storage не удаляются.
|
|
101
|
+
|
|
102
|
+
Memory capture сохраняет прежний контракт: runtime/query/mutation возвращают
|
|
103
|
+
`Promise<ErrorReportDraft | undefined>`, прямой draft capture —
|
|
104
|
+
`ErrorReportDraft | undefined`. В production создаётся обычный draft в памяти;
|
|
105
|
+
SSR/dev по-прежнему не создают draft. Поэтому UI boundary может продолжать
|
|
106
|
+
использовать `.then` и report ID. Сбор QueryClient diagnostics из памяти остаётся
|
|
107
|
+
доступным; `collectPersistedQueryDiagnostics()` возвращает `[]` без открытия IDB.
|
|
108
|
+
|
|
109
|
+
Вызов не отменяет native I/O, начатый ранее. Однако его поздний persisted result
|
|
110
|
+
не попадёт в memory draft, а после отключения drafts больше не пишутся в browser
|
|
111
|
+
storage. Для строгого отсутствия чтений выбирать режим нужно в bootstrap.
|
|
112
|
+
|
|
113
|
+
**Durable queue независима.** `createErrorReportQueue()` — явный storage API;
|
|
114
|
+
эта функция его не переключает. Приложение отдельно исключает создание очереди,
|
|
115
|
+
enqueue и запуск delivery в документе без persistence. Вызов также не меняет
|
|
116
|
+
Query persistence, Service Worker, пользовательские черновики или transport.
|
|
117
|
+
|
|
79
118
|
## Compile-time константы
|
|
80
119
|
|
|
81
120
|
Потребитель должен определить:
|
|
@@ -430,7 +469,7 @@ type ErrorReportDraft = {
|
|
|
430
469
|
};
|
|
431
470
|
```
|
|
432
471
|
|
|
433
|
-
`reportId` — UUID отдельного отчёта. `sessionId` стабилен в текущем `sessionStorage
|
|
472
|
+
`reportId` — UUID отдельного отчёта. По умолчанию `sessionId` стабилен в текущем `sessionStorage`; после browser opt-out используется отдельный ID только в памяти документа. `createdUtc` формируется как OData datetime UTC.
|
|
434
473
|
|
|
435
474
|
Payload содержит category/source/error, build environment, client, location, viewport, breadcrumbs и optional секции query/mutation/react/context.
|
|
436
475
|
|
|
@@ -452,11 +491,11 @@ parseErrorReportDrafts(value);
|
|
|
452
491
|
|
|
453
492
|
### Хранение
|
|
454
493
|
|
|
455
|
-
|
|
494
|
+
По умолчанию versioned drafts сохраняются под ключом `${__APP_ID__}.errorReport.drafts.v2`. В одной вкладке остаются последние 10 элементов. Старые удаляются с начала массива. Предыдущая unversioned storage-форма не загружается. После browser opt-out тот же лимит применяется к отдельному memory store без чтения или удаления browser records.
|
|
456
495
|
|
|
457
496
|
Storage errors и некорректный JSON подавляются. При переполнении sessionStorage текущее in-memory состояние обновится, но запись на диск может не сохраниться.
|
|
458
497
|
|
|
459
|
-
|
|
498
|
+
Импорт не загружает drafts. В обычном режиме они читаются лениво при первом обращении к draft API, после чего модульное состояние разделяют consumers одного bundle. В memory-режиме чтения browser drafts нет.
|
|
460
499
|
|
|
461
500
|
## Durable IndexedDB queue
|
|
462
501
|
|
|
@@ -708,7 +747,7 @@ Foundation удаляет известные secret-like keys и email, но н
|
|
|
708
747
|
|
|
709
748
|
### Мутировать результат `getErrorReportDrafts()`
|
|
710
749
|
|
|
711
|
-
Используйте `updateErrorReportDraft`; прямое изменение обходит
|
|
750
|
+
Используйте `updateErrorReportDraft`; прямое изменение обходит валидацию lifecycle и выбранное хранилище drafts.
|
|
712
751
|
|
|
713
752
|
### Передавать `UseQueryResult` вместо cache Query
|
|
714
753
|
|