@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.
Files changed (59) hide show
  1. package/README.md +2 -0
  2. package/dist/analytical-table/aggregation.d.ts +4 -0
  3. package/dist/analytical-table/aggregation.d.ts.map +1 -0
  4. package/dist/analytical-table/buildAnalyticalTableModel.d.ts +4 -0
  5. package/dist/analytical-table/buildAnalyticalTableModel.d.ts.map +1 -0
  6. package/dist/analytical-table/calculationTypes.d.ts +11 -0
  7. package/dist/analytical-table/calculationTypes.d.ts.map +1 -0
  8. package/dist/analytical-table/calculations.d.ts +6 -0
  9. package/dist/analytical-table/calculations.d.ts.map +1 -0
  10. package/dist/analytical-table/columnTypes.d.ts +30 -0
  11. package/dist/analytical-table/columnTypes.d.ts.map +1 -0
  12. package/dist/analytical-table/dataTypes.d.ts +14 -0
  13. package/dist/analytical-table/dataTypes.d.ts.map +1 -0
  14. package/dist/analytical-table/dateColumnTypes.d.ts +31 -0
  15. package/dist/analytical-table/dateColumnTypes.d.ts.map +1 -0
  16. package/dist/analytical-table/dateColumns.d.ts +11 -0
  17. package/dist/analytical-table/dateColumns.d.ts.map +1 -0
  18. package/dist/analytical-table/expansion.d.ts +4 -0
  19. package/dist/analytical-table/expansion.d.ts.map +1 -0
  20. package/dist/analytical-table/exportProjection.d.ts +4 -0
  21. package/dist/analytical-table/exportProjection.d.ts.map +1 -0
  22. package/dist/analytical-table/exportTypes.d.ts +31 -0
  23. package/dist/analytical-table/exportTypes.d.ts.map +1 -0
  24. package/dist/analytical-table/filtering.d.ts +4 -0
  25. package/dist/analytical-table/filtering.d.ts.map +1 -0
  26. package/dist/analytical-table/index.d.ts +8 -0
  27. package/dist/analytical-table/index.d.ts.map +1 -0
  28. package/dist/analytical-table/index.js +364 -0
  29. package/dist/analytical-table/index.js.map +1 -0
  30. package/dist/analytical-table/indexAnalyticalRows.d.ts +4 -0
  31. package/dist/analytical-table/indexAnalyticalRows.d.ts.map +1 -0
  32. package/dist/analytical-table/modelTypes.d.ts +43 -0
  33. package/dist/analytical-table/modelTypes.d.ts.map +1 -0
  34. package/dist/analytical-table/types.d.ts +8 -0
  35. package/dist/analytical-table/types.d.ts.map +1 -0
  36. package/dist/analytical-table/valueComparison.d.ts +5 -0
  37. package/dist/analytical-table/valueComparison.d.ts.map +1 -0
  38. package/dist/analytical-table/viewTypes.d.ts +24 -0
  39. package/dist/analytical-table/viewTypes.d.ts.map +1 -0
  40. package/dist/chunks/{formatters-hHUJpZqD.js → formatters-CEltML3y.js} +224 -224
  41. package/dist/chunks/{formatters-hHUJpZqD.js.map → formatters-CEltML3y.js.map} +1 -1
  42. package/dist/chunks/{validate-DnkaY38p.js → validate-M2zPiLXn.js} +24 -24
  43. package/dist/chunks/{validate-DnkaY38p.js.map → validate-M2zPiLXn.js.map} +1 -1
  44. package/dist/error-report/diagnostics.d.ts.map +1 -1
  45. package/dist/error-report/drafts.d.ts.map +1 -1
  46. package/dist/error-report/errorReportStorage.d.ts +18 -0
  47. package/dist/error-report/errorReportStorage.d.ts.map +1 -0
  48. package/dist/error-report/index.d.ts +1 -0
  49. package/dist/error-report/index.d.ts.map +1 -1
  50. package/dist/error-report/index.js +557 -540
  51. package/dist/error-report/index.js.map +1 -1
  52. package/dist/excel/index.js +36 -36
  53. package/dist/excel/index.js.map +1 -1
  54. package/dist/formatters/index.js +2 -2
  55. package/dist/hooks/useFloatingListbox.d.ts +1 -1
  56. package/dist/odata-service/formatters.d.ts +2 -2
  57. package/package.json +5 -1
  58. package/src/analytical-table/README.mdx +169 -0
  59. 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`. `createdUtc` формируется как OData datetime UTC.
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
- Versioned drafts сохраняются под ключом `${__APP_ID__}.errorReport.drafts.v2`. В одной вкладке остаются последние 10 элементов. Старые удаляются с начала массива. Предыдущая unversioned storage-форма не загружается.
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
- Модуль загружает drafts при первом импорте. Это модульное состояние, общее для всех consumers одного bundle.
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`; прямое изменение обходит сохранение в sessionStorage.
750
+ Используйте `updateErrorReportDraft`; прямое изменение обходит валидацию lifecycle и выбранное хранилище drafts.
712
751
 
713
752
  ### Передавать `UseQueryResult` вместо cache Query
714
753