@ryuzaki13/react-foundation-lib 1.4.1 → 1.4.2

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 (73) hide show
  1. package/README.md +154 -100
  2. package/dist/chunks/{formatters-CBgrBeSA.js → formatters-D33Uh3si.js} +7 -8
  3. package/dist/chunks/{formatters-CBgrBeSA.js.map → formatters-D33Uh3si.js.map} +1 -1
  4. package/dist/chunks/odata-service-CWkHASdw.js +2211 -0
  5. package/dist/chunks/odata-service-CWkHASdw.js.map +1 -0
  6. package/dist/chunks/{validate-BW-7BZy_.js → validate-DyzojM0w.js} +2 -2
  7. package/dist/chunks/{validate-BW-7BZy_.js.map → validate-DyzojM0w.js.map} +1 -1
  8. package/dist/error-report/index.js +2 -2
  9. package/dist/excel/index.js +20 -21
  10. package/dist/excel/index.js.map +1 -1
  11. package/dist/formatters/index.js +4 -5
  12. package/dist/{odata → odata-service}/buildSeparatedArrays.d.ts +1 -1
  13. package/dist/odata-service/buildSeparatedArrays.d.ts.map +1 -0
  14. package/dist/{odata/types.d.ts → odata-service/collectionTypes.d.ts} +1 -1
  15. package/dist/odata-service/collectionTypes.d.ts.map +1 -0
  16. package/dist/{odata → odata-service}/dateUtils.d.ts +1 -1
  17. package/dist/odata-service/dateUtils.d.ts.map +1 -0
  18. package/dist/{odata → odata-service}/findCollectionPairs.d.ts +1 -1
  19. package/dist/odata-service/findCollectionPairs.d.ts.map +1 -0
  20. package/dist/odata-service/index.d.ts +5 -0
  21. package/dist/odata-service/index.d.ts.map +1 -1
  22. package/dist/odata-service/index.js +2 -2
  23. package/dist/{odata/store.d.ts → odata-service/odataCollectionStore.d.ts} +1 -1
  24. package/dist/odata-service/odataCollectionStore.d.ts.map +1 -0
  25. package/dist/query-client/index.js.map +1 -1
  26. package/dist/query-client/queryClient.d.ts +1 -3
  27. package/dist/query-client/queryClient.d.ts.map +1 -1
  28. package/package.json +1 -5
  29. package/src/bounded-copy-stack/README.mdx +257 -0
  30. package/src/context-menu/README.mdx +326 -0
  31. package/src/currency/README.mdx +155 -0
  32. package/src/date-segments/README.mdx +319 -0
  33. package/src/error/README.mdx +95 -0
  34. package/src/error-report/README.mdx +612 -0
  35. package/src/excel/README.mdx +449 -0
  36. package/src/fingerprint/README.mdx +100 -0
  37. package/src/form/README.mdx +101 -0
  38. package/src/foundationLib.mdx +70 -54
  39. package/src/hooks/README.mdx +988 -0
  40. package/src/http/README.mdx +126 -0
  41. package/src/media/README.mdx +1853 -0
  42. package/src/notifications/README.mdx +323 -0
  43. package/src/number-scale/README.mdx +318 -0
  44. package/src/odata-service/README.mdx +2916 -0
  45. package/src/presets/README.mdx +136 -0
  46. package/src/pwa/README.mdx +394 -0
  47. package/src/query-client/README.mdx +2500 -0
  48. package/src/range-output/README.mdx +161 -0
  49. package/src/seo/README.mdx +279 -0
  50. package/src/session-storage/README.mdx +125 -0
  51. package/src/string-comparison/README.mdx +96 -0
  52. package/src/table/README.mdx +3106 -0
  53. package/src/three-scene/README.mdx +248 -0
  54. package/src/types/README.mdx +172 -0
  55. package/src/utils/README.mdx +1724 -0
  56. package/src/validators/README.mdx +239 -0
  57. package/src/virtualizer/README.mdx +138 -0
  58. package/src/xml/README.mdx +149 -0
  59. package/dist/chunks/dateUtils-B5V5aXB-.js +0 -847
  60. package/dist/chunks/dateUtils-B5V5aXB-.js.map +0 -1
  61. package/dist/chunks/odata-service-B6TxBSLm.js +0 -1277
  62. package/dist/chunks/odata-service-B6TxBSLm.js.map +0 -1
  63. package/dist/odata/buildSeparatedArrays.d.ts.map +0 -1
  64. package/dist/odata/dateUtils.d.ts.map +0 -1
  65. package/dist/odata/findCollectionPairs.d.ts.map +0 -1
  66. package/dist/odata/index.d.ts +0 -6
  67. package/dist/odata/index.d.ts.map +0 -1
  68. package/dist/odata/index.js +0 -102
  69. package/dist/odata/index.js.map +0 -1
  70. package/dist/odata/store.d.ts.map +0 -1
  71. package/dist/odata/types.d.ts.map +0 -1
  72. package/src/pwa/README.md +0 -638
  73. package/src/query-client/README.md +0 -565
@@ -0,0 +1,612 @@
1
+ import { Meta } from "@storybook/addon-docs/blocks";
2
+
3
+ <Meta title="Foundation Lib/Errors/Error Report" />
4
+
5
+ # Error Report
6
+
7
+ Модуль `@ryuzaki13/react-foundation-lib/error-report` собирает клиентскую диагностику ошибки в структурированный draft и временно хранит последние drafts в `sessionStorage`.
8
+
9
+ Он умеет объединить:
10
+
11
+ - нормализованную ошибку и stack trace;
12
+ - сведения о browser/build;
13
+ - URL и viewport;
14
+ - последние действия пользователя — breadcrumbs;
15
+ - состояние TanStack Query без полного query data;
16
+ - технические сведения о persisted queries;
17
+ - контекст React/runtime/transport;
18
+ - lifecycle доставки: `pending`, `sending`, `sent`, `failed`.
19
+
20
+ Важная граница: **lib-пакет не отправляет отчёт на сервер**. Он формирует draft и предоставляет точки подключения. Авторизация, HTTP/API delivery, retry, пользовательское согласие и UI принадлежат приложению либо API-пакету.
21
+
22
+ ## Установка
23
+
24
+ Модуль интегрируется с TanStack Query и query persistence.
25
+
26
+ ```bash
27
+ npm install @ryuzaki13/react-foundation-lib \
28
+ @tanstack/react-query \
29
+ @tanstack/query-persist-client-core
30
+ ```
31
+
32
+ ```ts
33
+ import {
34
+ captureRuntimeErrorReport,
35
+ createErrorInfo,
36
+ installErrorReportBrowserBreadcrumbs,
37
+ setErrorReportRuntimeErrorReporter,
38
+ type ErrorReportDraft
39
+ } from "@ryuzaki13/react-foundation-lib/error-report";
40
+ ```
41
+
42
+ ## Ментальная модель
43
+
44
+ ```text
45
+ исходная ошибка
46
+ → capture-функция
47
+ → нормализация + diagnostics + breadcrumbs
48
+ → ErrorReportDraft(status = pending)
49
+ → sessionStorage, максимум 10 drafts
50
+ → app/API delivery
51
+ → updateErrorReportDraft(status = sending/sent/failed)
52
+ ```
53
+
54
+ Сбор отчёта — дополнительный диагностический сценарий. Ошибка репортера не должна заменять исходную ошибку и ломать основной пользовательский поток.
55
+
56
+ ## Когда сбор включён
57
+
58
+ ### `isErrorReportingEnabled(options?)`
59
+
60
+ ```ts
61
+ isErrorReportingEnabled();
62
+ isErrorReportingEnabled({ isDev: false });
63
+ ```
64
+
65
+ Возвращает `true` только в браузере и только вне dev-режима. По умолчанию режим читается из compile-time `__DEV__`.
66
+
67
+ Следствия:
68
+
69
+ - при SSR draft не создаётся;
70
+ - в development capture-функции возвращают `undefined`;
71
+ - тест может явно передать `{ isDev: false }` только в эту функцию, но остальные capture-функции всё равно используют общий `__DEV__`.
72
+
73
+ ## Compile-time константы
74
+
75
+ Потребитель должен определить:
76
+
77
+ ```ts
78
+ declare const __DEV__: boolean;
79
+ declare const __APP_ID__: string;
80
+ declare const __APP_BUILD_ID__: string;
81
+ ```
82
+
83
+ `__APP_ID__` входит в ключи `sessionStorage`, а `__APP_BUILD_ID__` помогает связать ошибку с деплоем.
84
+
85
+ Пример Vite:
86
+
87
+ ```ts
88
+ define: {
89
+ __DEV__: JSON.stringify(mode !== "production"),
90
+ __APP_ID__: JSON.stringify("education-arm"),
91
+ __APP_BUILD_ID__: JSON.stringify(process.env.BUILD_ID ?? "local"),
92
+ }
93
+ ```
94
+
95
+ Модуль persisted diagnostics использует инфраструктуру `/query-client`; её compile-time persistence-константы тоже должны быть настроены, если приложение включает соответствующую часть query persistence.
96
+
97
+ ## Быстрый старт для runtime-ошибок
98
+
99
+ ### Прямой capture
100
+
101
+ ```ts
102
+ try {
103
+ await runImport();
104
+ } catch (error) {
105
+ const draft = await captureRuntimeErrorReport(error, {
106
+ source: "users-import",
107
+ detail: {
108
+ fileType: selectedFile.type,
109
+ step: "upload"
110
+ }
111
+ });
112
+
113
+ if (draft) {
114
+ await sendDraft(draft);
115
+ }
116
+
117
+ throw error;
118
+ }
119
+ ```
120
+
121
+ ### Через независимый runtime facade
122
+
123
+ Общий код может не знать о QueryClient и delivery. Приложение один раз регистрирует reporter:
124
+
125
+ ```ts
126
+ setErrorReportRuntimeErrorReporter(async (error, context) => {
127
+ const draft = await captureRuntimeErrorReport(error, context, queryClient);
128
+
129
+ if (draft) {
130
+ await deliverErrorReport(draft);
131
+ }
132
+ });
133
+ ```
134
+
135
+ После этого любой нижний слой может безопасно вызвать:
136
+
137
+ ```ts
138
+ await reportRuntimeError(error, {
139
+ source: "document-download",
140
+ detail: { action: "download" }
141
+ });
142
+ ```
143
+
144
+ `reportRuntimeError` ничего не делает без зарегистрированного reporter и подавляет ошибки самого reporter. Для отключения:
145
+
146
+ ```ts
147
+ setErrorReportRuntimeErrorReporter(undefined);
148
+ ```
149
+
150
+ Одновременно хранится один runtime reporter. Новая регистрация заменяет предыдущую и очищает внутреннее окно runtime-дедупликации.
151
+
152
+ ## Нормализация ошибки
153
+
154
+ ### `createErrorInfo(error)`
155
+
156
+ Любое брошенное значение преобразуется к форме:
157
+
158
+ ```ts
159
+ type ErrorReportErrorInfo = {
160
+ name: string;
161
+ message: string;
162
+ code?: string;
163
+ stackTrace?: string;
164
+ httpStatus?: number;
165
+ };
166
+ ```
167
+
168
+ Для обычного `Error` сохраняются `name`, непустой `message` и полный `stack`.
169
+
170
+ ```ts
171
+ createErrorInfo(new Error("Не удалось сохранить"));
172
+ // { name: "Error", message: "Не удалось сохранить", stackTrace: "..." }
173
+ ```
174
+
175
+ Пустое сообщение, пустая строка и строка из пробелов заменяются на «Неизвестная ошибка».
176
+
177
+ Для plain object читаются `name`, `message`, `stack` и первый из числовых `status`, `statusCode`, `httpStatus`. Другие поля объекта автоматически не включаются.
178
+
179
+ Также распознаётся serverFn transport-форма с `error.appError`: она превращается в `name: "AppError"`, сохраняет `message`, `code` и status.
180
+
181
+ ## Безопасные диагностические значения
182
+
183
+ ### `createDiagnosticValue(value, depth?)`
184
+
185
+ Преобразует произвольное значение в JSON-совместимое дерево `ErrorReportSafeValue`.
186
+
187
+ Правила ограничения:
188
+
189
+ - максимальная глубина — 4;
190
+ - максимум 20 элементов массива;
191
+ - максимум 40 ключей объекта;
192
+ - `undefined`, function, symbol и усечённая ветка получают специальные объекты `{ type: ... }`;
193
+ - `bigint` превращается в строку;
194
+ - `Date` — в ISO-строку.
195
+
196
+ ```ts
197
+ createDiagnosticValue({ retry: 2, handler: () => undefined });
198
+ // { retry: 2, handler: { type: "function" } }
199
+ ```
200
+
201
+ Критически важно: функция **не скрывает значения по имени ключа**. Поля `token`, `password`, `authorization`, email и другие секреты сохранятся, если их передать. Это ограничитель размера и типа, а не security sanitizer.
202
+
203
+ ### `sanitizeDetail(detail)`
204
+
205
+ Применяет `createDiagnosticValue` к первым 40 полям объекта. Для `undefined` или пустого объекта возвращает `undefined`.
206
+
207
+ Передавайте только заранее отобранные технические данные:
208
+
209
+ ```ts
210
+ sanitizeDetail({
211
+ operation: "save-order",
212
+ entitySet: "Orders",
213
+ rowCount: rows.length
214
+ });
215
+ ```
216
+
217
+ Не передавайте request body, access token, cookie, персональные поля и полные backend responses.
218
+
219
+ ### `createDataShape(value, depth?)`
220
+
221
+ Описывает структуру данных без leaf-значений:
222
+
223
+ ```ts
224
+ createDataShape({ rows: [{ id: "42", amount: 100 }] });
225
+ // объект с keys/shape, массивом length и shape первых трёх элементов
226
+ ```
227
+
228
+ Для массива сохраняются длина и формы первых трёх элементов. Для объекта — первые 40 ключей и их формы. Именно эта функция используется для query data, чтобы не копировать содержимое кэша.
229
+
230
+ ## Breadcrumbs
231
+
232
+ Breadcrumb — короткая запись о событии перед ошибкой.
233
+
234
+ ```ts
235
+ type ErrorReportBreadcrumb = {
236
+ utc: string;
237
+ type: "route" | "click" | "visibility" | "query-error" | "mutation-error" | "runtime-error";
238
+ routeId?: string;
239
+ appId?: string;
240
+ viewId?: string;
241
+ target?: string;
242
+ detail?: Record<string, ErrorReportSafeValue>;
243
+ };
244
+ ```
245
+
246
+ ### Ручное добавление
247
+
248
+ ```ts
249
+ addErrorReportBreadcrumb({
250
+ type: "route",
251
+ routeId: "/orders/$id",
252
+ detail: sanitizeDetail({ orderId: "42" })
253
+ });
254
+ ```
255
+
256
+ Если `utc` отсутствует, используется текущий ISO timestamp. В памяти остаются последние 80 breadcrumbs.
257
+
258
+ ```ts
259
+ getErrorReportBreadcrumbs();
260
+ clearErrorReportBreadcrumbs();
261
+ ```
262
+
263
+ Возвращаемый массив является текущим внутренним массивом, а не глубокой копией. Считайте его readonly и не меняйте элементы вручную.
264
+
265
+ ### Автоматические browser breadcrumbs
266
+
267
+ ```ts
268
+ const uninstallBreadcrumbs = installErrorReportBrowserBreadcrumbs();
269
+
270
+ // При демонтаже client shell:
271
+ uninstallBreadcrumbs();
272
+ ```
273
+
274
+ Установщик слушает:
275
+
276
+ - click в capture phase;
277
+ - `document.visibilitychange`.
278
+
279
+ Для click сохраняются координаты/модификаторы и ограниченное описание meaningful target: tag, стабильные атрибуты, короткий текст и цепочка родителей. Глубина DOM-цепочки ограничена 20, текст — 80 символами, атрибут — 120.
280
+
281
+ Код не читает `input.value` и не копирует CSS class list, но текст/ARIA/title/data-атрибуты тоже могут содержать чувствительные данные. Не помещайте секреты в DOM-атрибуты и UI-текст.
282
+
283
+ На сервере установщик возвращает пустую cleanup-функцию.
284
+
285
+ ## Диагностика TanStack Query
286
+
287
+ ### `collectQueryDiagnostics(query)`
288
+
289
+ Собирает:
290
+
291
+ - `queryHash` и диагностическое представление `queryKey`;
292
+ - status/fetchStatus;
293
+ - timestamps обновления data/error;
294
+ - failure count, invalidation и observer count;
295
+ - meta;
296
+ - `dataShape` вместо data;
297
+ - нормализованную query error.
298
+
299
+ Значения `queryKey` и `meta` сохраняются. Если туда попал token или персональный идентификатор, он попадёт в отчёт. Проектируйте query keys как диагностически безопасный контракт.
300
+
301
+ ### `collectMutationDiagnostics(mutation)`
302
+
303
+ Собирает mutation key, status, failure count, submitted timestamp, meta и error. Mutation variables/data не копируются.
304
+
305
+ ### `collectQueryClientDiagnostics(queryClient)`
306
+
307
+ Возвращает последние 40 query и последние 40 mutations из соответствующих cache. Это синхронный snapshot текущего QueryClient.
308
+
309
+ ### `collectPersistedQueryDiagnostics()`
310
+
311
+ Асинхронно читает IndexedDB persistence из `/query-client` и возвращает максимум 40 записей.
312
+
313
+ Сохраняются storage key, buster, query hash/key, statuses, timestamps, failure count и error. `state.data` намеренно исключён полностью.
314
+
315
+ Если IndexedDB/persistence недоступны или чтение бросает ошибку, возвращается пустой массив — сбор отчёта не падает.
316
+
317
+ ## Capture query-ошибки
318
+
319
+ ### `captureQueryErrorReport(error, query, queryClient, context?)`
320
+
321
+ ```ts
322
+ const draft = await captureQueryErrorReport(error, query, queryClient, {
323
+ source: "orders-list",
324
+ detail: { routeId: "/orders" }
325
+ });
326
+ ```
327
+
328
+ Функция:
329
+
330
+ 1. подавляет отложенный transport-report для того же экземпляра `Error`;
331
+ 2. добавляет breadcrumb `query-error`;
332
+ 3. параллельно читает persisted diagnostics и QueryClient snapshot;
333
+ 4. собирает подробности конкретной query;
334
+ 5. создаёт draft категории `query`.
335
+
336
+ Возвращает `ErrorReportDraft | undefined`. В development/SSR будет `undefined`.
337
+
338
+ `context.errorInfo` позволяет передать уже нормализованную ошибку, `context.queryKey` — переопределить breadcrumb key, `context.detail` — добавить контролируемый context.
339
+
340
+ Параметр `query` — реальный объект TanStack Query cache, а не `UseQueryResult` и не query options.
341
+
342
+ ## Capture mutation-ошибки
343
+
344
+ ### `captureMutationErrorReport(error, mutation, queryClient, context?)`
345
+
346
+ Работает аналогично query capture: подавляет дублирующий transport-report, добавляет mutation breadcrumb и собирает QueryClient/persistence snapshot.
347
+
348
+ ```ts
349
+ const draft = await captureMutationErrorReport(error, mutation, queryClient, { source: "save-order", detail: { form: "order-edit" } });
350
+ ```
351
+
352
+ Текущая реализация включает `detail` в breadcrumb, но не переносит его в `payload.context` mutation draft. Если этот контекст критичен для доставки, добавьте безопасные данные в app-level envelope либо используйте явную согласованную модель до capture.
353
+
354
+ ## Capture runtime/React-ошибки
355
+
356
+ ### `captureRuntimeErrorReport(error, context?, queryClient?)`
357
+
358
+ ```ts
359
+ const draft = await captureRuntimeErrorReport(
360
+ error,
361
+ {
362
+ category: "react",
363
+ source: "error-boundary",
364
+ componentStack: info.componentStack,
365
+ detail: { routeId }
366
+ },
367
+ queryClient
368
+ );
369
+ ```
370
+
371
+ Без `queryClient` отчёт создаётся без query/persisted diagnostics. Это полезно для низкоуровневого runtime кода. С QueryClient добавляется общий snapshot.
372
+
373
+ По умолчанию category/source — `runtime`. `componentStack` записывается в секцию `react`.
374
+
375
+ Одинаковые runtime/React capture в браузере объединяются примерно на 60 секунд по fingerprint ошибки, source, path, component stack и detail. Параллельные вызовы получают одну Promise/draft. Это защищает от лавины одинаковых отчётов. После отклонения capture внутренняя запись удаляется.
376
+
377
+ ## Draft и payload
378
+
379
+ ```ts
380
+ type ErrorReportStatus = "pending" | "sending" | "sent" | "failed";
381
+
382
+ type ErrorReportDraft = {
383
+ reportId: string;
384
+ sessionId: string;
385
+ createdUtc: string;
386
+ category: ErrorReportCategory;
387
+ status: ErrorReportStatus;
388
+ sentUtc?: string;
389
+ failedReason?: string;
390
+ payload: ErrorReportPayload;
391
+ };
392
+ ```
393
+
394
+ `reportId` — UUID отдельного отчёта. `sessionId` стабилен в текущем `sessionStorage`. `createdUtc` формируется как OData datetime UTC.
395
+
396
+ Payload содержит category/source/error, build environment, client, location, viewport, breadcrumbs и optional секции query/mutation/react/context.
397
+
398
+ ### Хранение
399
+
400
+ Drafts сохраняются под ключом `${__APP_ID__}.errorReport.drafts.v1`. В одной вкладке остаются последние 10 элементов. Старые удаляются с начала массива.
401
+
402
+ Storage errors и некорректный JSON подавляются. При переполнении sessionStorage текущее in-memory состояние обновится, но запись на диск может не сохраниться.
403
+
404
+ Модуль загружает drafts при первом импорте. Это модульное состояние, общее для всех consumers одного bundle.
405
+
406
+ ## Низкоуровневый `captureErrorReportDraft(args)`
407
+
408
+ Используйте, когда все diagnostic sections уже подготовлены вручную:
409
+
410
+ ```ts
411
+ const draft = captureErrorReportDraft({
412
+ category: "runtime",
413
+ source: "custom-engine",
414
+ error: createErrorInfo(error),
415
+ context: sanitizeDetail({ stage: "compile" })
416
+ });
417
+ ```
418
+
419
+ Функция добавит report/session IDs, environment, client/location/viewport и текущие breadcrumbs. Она синхронная и не собирает QueryClient автоматически.
420
+
421
+ ## Чтение и обновление drafts
422
+
423
+ ```ts
424
+ const drafts = getErrorReportDrafts();
425
+ const draft = getErrorReportDraft(reportId);
426
+ ```
427
+
428
+ Возвращаются ссылки из внутреннего массива. Считайте их readonly; для lifecycle используйте `updateErrorReportDraft`.
429
+
430
+ ```ts
431
+ updateErrorReportDraft(reportId, { status: "sending" });
432
+
433
+ try {
434
+ await api.sendErrorReport(draft.payload);
435
+ updateErrorReportDraft(reportId, {
436
+ status: "sent",
437
+ sentUtc: new Date().toISOString(),
438
+ failedReason: undefined
439
+ });
440
+ } catch (error) {
441
+ updateErrorReportDraft(reportId, {
442
+ status: "failed",
443
+ failedReason: createErrorInfo(error).message
444
+ });
445
+ }
446
+ ```
447
+
448
+ Для неизвестного ID update возвращает `undefined`. Обновление поверхностное и принимает `Partial<ErrorReportDraft>`: библиотека не проверяет разрешённые переходы статусов. App delivery обязан не менять `reportId`, payload и другие identity-поля без необходимости.
449
+
450
+ ## Environment API
451
+
452
+ ### `getErrorReportEnvironment()`
453
+
454
+ Возвращает mode из `__DEV__` и build ID.
455
+
456
+ ### `getErrorReportClientEnvironment()`
457
+
458
+ В браузере возвращает user agent, language и timezone. На сервере — `undefined`.
459
+
460
+ Эти поля могут считаться персональными/идентифицирующими данными в политике организации. Согласуйте срок хранения и доступ к отчётам.
461
+
462
+ ## Transport reporter
463
+
464
+ Transport-слой может сообщить об ошибке parsing/fetch, не зная о QueryClient и UI.
465
+
466
+ ### Регистрация
467
+
468
+ ```ts
469
+ setErrorReportTransportErrorReporter(async (error, context) => {
470
+ const draft = await captureRuntimeErrorReport(
471
+ error,
472
+ {
473
+ source: context.source,
474
+ detail: context
475
+ },
476
+ queryClient
477
+ );
478
+
479
+ if (draft) {
480
+ await deliverErrorReport(draft);
481
+ }
482
+ });
483
+ ```
484
+
485
+ Для отключения передайте `undefined`. Регистрация очищает недавние transport keys.
486
+
487
+ ### `reportTransportError(error, context)`
488
+
489
+ Context имеет source `fetch.parseResponse` или `fetch.buildHttpError` и может содержать URL, method, SAP client, status/content type, redirect/response type и безопасное summary HTML-response.
490
+
491
+ Вызов планирует reporter через 250 мс. Задержка позволяет TanStack Query перехватить тот же `Error` и создать более полезный query/mutation report.
492
+
493
+ ### Подавление дубля
494
+
495
+ ```ts
496
+ isTransportErrorReportScheduled(error);
497
+ suppressTransportErrorReport(error);
498
+ ```
499
+
500
+ Capture query/mutation вызывает suppression автоматически. Проверка работает только для того же экземпляра `Error`, потому что внутри используются WeakSet.
501
+
502
+ Transport reports дополнительно дедуплицируются на 60 секунд по source, method, status и URL. Хранится ограниченное число недавних ключей.
503
+
504
+ Если reporter отсутствует, подавлен, недавно уже сработал или сам бросил ошибку, основной transport-сценарий не ломается.
505
+
506
+ ## Рекомендуемая app-level интеграция
507
+
508
+ ```ts
509
+ export function installErrorReporting(queryClient: QueryClient) {
510
+ const uninstallBreadcrumbs = installErrorReportBrowserBreadcrumbs();
511
+
512
+ setErrorReportRuntimeErrorReporter(async (error, context) => {
513
+ const draft = await captureRuntimeErrorReport(error, context, queryClient);
514
+ if (draft) await deliverDraft(draft);
515
+ });
516
+
517
+ setErrorReportTransportErrorReporter(async (error, context) => {
518
+ const draft = await captureRuntimeErrorReport(
519
+ error,
520
+ { source: context.source, detail: pickSafeTransportFields(context) },
521
+ queryClient
522
+ );
523
+ if (draft) await deliverDraft(draft);
524
+ });
525
+
526
+ return () => {
527
+ uninstallBreadcrumbs();
528
+ setErrorReportRuntimeErrorReporter(undefined);
529
+ setErrorReportTransportErrorReporter(undefined);
530
+ };
531
+ }
532
+ ```
533
+
534
+ Создавайте lifecycle один раз в client shell. Функция `pickSafeTransportFields` — ответственность приложения и должна явно разрешать поля, а не копировать произвольный объект.
535
+
536
+ ## Безопасность и приватность
537
+
538
+ Перед включением production delivery обязательно определите data policy.
539
+
540
+ ### Что намеренно не сохраняется
541
+
542
+ - полное `query.state.data`;
543
+ - persisted `state.data`;
544
+ - mutation variables;
545
+ - значения input при автоматическом click breadcrumb;
546
+ - тела transport-запросов.
547
+
548
+ ### Что всё ещё может содержать данные
549
+
550
+ - queryKey и mutationKey;
551
+ - query/mutation meta;
552
+ - `context.detail`;
553
+ - URL и pathname;
554
+ - error message/stack;
555
+ - UI text, ARIA/title/data attributes breadcrumbs;
556
+ - user agent, language, timezone;
557
+ - имена persisted storage keys.
558
+
559
+ Поэтому:
560
+
561
+ 1. не помещайте секреты в query keys/meta;
562
+ 2. собирайте detail по allow-list;
563
+ 3. очищайте URL от token/query secrets до передачи в custom context;
564
+ 4. защищайте endpoint доставки;
565
+ 5. задайте срок хранения и права доступа;
566
+ 6. при необходимости запрашивайте согласие пользователя;
567
+ 7. не показывайте необработанный stack trace всем пользователям.
568
+
569
+ ## Частые ошибки
570
+
571
+ ### Ожидать автоматическую отправку
572
+
573
+ Capture только создаёт draft. Delivery нужно явно подключить.
574
+
575
+ ### Использовать `createDiagnosticValue` как redactor
576
+
577
+ Он ограничивает размер, но сохраняет значения независимо от имени поля.
578
+
579
+ ### Вызывать production capture в development и ждать draft
580
+
581
+ По контракту вернётся `undefined`.
582
+
583
+ ### Не очищать глобальные reporters
584
+
585
+ При hot reload, тестах или размонтировании shell снимайте bindings, чтобы старый QueryClient/delivery не оставался в closure.
586
+
587
+ ### Мутировать результат `getErrorReportDrafts()`
588
+
589
+ Используйте `updateErrorReportDraft`; прямое изменение обходит сохранение в sessionStorage.
590
+
591
+ ### Передавать `UseQueryResult` вместо cache Query
592
+
593
+ `captureQueryErrorReport` нужен объект Query, обычно доступный внутри QueryCache callbacks/instrumentation.
594
+
595
+ ### Создать цикл отчётов
596
+
597
+ Ошибка endpoint доставки не должна снова проходить через тот же автоматический transport reporter. Пометьте/исключите delivery transport или используйте отдельный канал.
598
+
599
+ ## Краткая карта API
600
+
601
+ | Группа | Экспорты |
602
+ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------- |
603
+ | Capture | `captureQueryErrorReport`, `captureMutationErrorReport`, `captureRuntimeErrorReport`, `captureErrorReportDraft` |
604
+ | Errors | `createErrorInfo` |
605
+ | Safe values | `createDiagnosticValue`, `createDataShape`, `sanitizeDetail` |
606
+ | Breadcrumbs | `addErrorReportBreadcrumb`, `getErrorReportBreadcrumbs`, `clearErrorReportBreadcrumbs`, `installErrorReportBrowserBreadcrumbs` |
607
+ | Diagnostics | `collectQueryDiagnostics`, `collectMutationDiagnostics`, `collectQueryClientDiagnostics`, `collectPersistedQueryDiagnostics` |
608
+ | Drafts | `getErrorReportDraft`, `getErrorReportDrafts`, `updateErrorReportDraft` |
609
+ | Environment | `isErrorReportingEnabled`, `getErrorReportEnvironment`, `getErrorReportClientEnvironment` |
610
+ | Runtime facade | `setErrorReportRuntimeErrorReporter`, `reportRuntimeError` |
611
+ | Transport facade | `setErrorReportTransportErrorReporter`, `reportTransportError`, `isTransportErrorReportScheduled`, `suppressTransportErrorReport` |
612
+ | Contracts | `ErrorReportDraft`, `ErrorReportPayload`, contexts, diagnostics и transport types |