@ryuzaki13/react-foundation-lib 1.9.0 → 2.0.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.
@@ -1,6 +1,7 @@
1
1
  import type { QueryKey } from "@tanstack/react-query";
2
2
  export type ErrorReportCategory = "query" | "mutation" | "runtime" | "react";
3
3
  export type ErrorReportStatus = "pending" | "sending" | "sent" | "failed";
4
+ export type ErrorReportPayloadVersion = 1;
4
5
  export type ErrorReportSafeValue = string | number | boolean | null | ErrorReportSafeValue[] | {
5
6
  [key: string]: ErrorReportSafeValue;
6
7
  };
@@ -43,7 +44,6 @@ export type ErrorReportMutationDiagnostics = {
43
44
  error?: ErrorReportErrorInfo;
44
45
  };
45
46
  export type ErrorReportPersistedQueryDiagnostics = {
46
- storageKey: string;
47
47
  buster?: string;
48
48
  queryHash?: string;
49
49
  queryKey?: ErrorReportSafeValue;
@@ -55,6 +55,8 @@ export type ErrorReportPersistedQueryDiagnostics = {
55
55
  error?: ErrorReportErrorInfo;
56
56
  };
57
57
  export type ErrorReportPayload = {
58
+ payloadVersion: ErrorReportPayloadVersion;
59
+ application: string;
58
60
  reportId: string;
59
61
  sessionId: string;
60
62
  createdUtc: string;
@@ -91,6 +93,12 @@ export type ErrorReportPayload = {
91
93
  persistedQueries?: ErrorReportPersistedQueryDiagnostics[];
92
94
  breadcrumbs: ErrorReportBreadcrumb[];
93
95
  context?: Record<string, ErrorReportSafeValue>;
96
+ truncation?: {
97
+ reason: "payload-size";
98
+ limitBytes: number;
99
+ originalBytes: number;
100
+ droppedSections: string[];
101
+ };
94
102
  };
95
103
  export type ErrorReportDraft = {
96
104
  reportId: string;
@@ -102,6 +110,12 @@ export type ErrorReportDraft = {
102
110
  failedReason?: string;
103
111
  payload: ErrorReportPayload;
104
112
  };
113
+ /** Единственные поля draft, которые delivery lifecycle вправе изменять. */
114
+ export type ErrorReportDraftLifecyclePatch = {
115
+ status?: ErrorReportStatus;
116
+ sentUtc?: string;
117
+ failedReason?: string;
118
+ };
105
119
  export type ErrorReportRuntimeContext = {
106
120
  category?: Extract<ErrorReportCategory, "runtime" | "react">;
107
121
  source?: string;
@@ -120,4 +134,15 @@ export type ErrorReportMutationContext = {
120
134
  errorInfo?: ErrorReportErrorInfo;
121
135
  detail?: Record<string, unknown>;
122
136
  };
137
+ export type ErrorReportSanitizationScope = "breadcrumb-detail" | "context" | "draft-failed-reason" | "error-message" | "mutation-key" | "mutation-meta" | "persisted-query-key" | "query-data-shape" | "query-key" | "query-meta" | "stack-trace";
138
+ export type ErrorReportSanitizationContext = {
139
+ scope: ErrorReportSanitizationScope;
140
+ source?: string;
141
+ };
142
+ /**
143
+ * App-level callback получает уже bounded JSON-совместимое значение и может
144
+ * применить предметный allow-list. Foundation повторно нормализует результат,
145
+ * поэтому callback не может обойти ограничения глубины, размера строк и циклов.
146
+ */
147
+ export type ErrorReportSanitizer = (value: ErrorReportSafeValue, context: ErrorReportSanitizationContext) => ErrorReportSafeValue | undefined;
123
148
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/error-report/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/error-report/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryuzaki13/react-foundation-lib",
3
- "version": "1.9.0",
3
+ "version": "2.0.0",
4
4
  "description": "Reusable React and TypeScript foundation utilities, hooks, and primitives.",
5
5
  "license": "MIT",
6
6
  "author": "Ryuzaki13",
@@ -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` собирает клиентскую диагностику ошибки в структурированный draft и временно хранит последние drafts в `sessionStorage`.
7
+ Модуль `@ryuzaki13/react-foundation-lib/error-report` собирает клиентскую диагностику ошибки в versioned bounded draft, временно хранит последние drafts в `sessionStorage` и предоставляет durable IndexedDB queue для отложенной доставки.
8
8
 
9
9
  Он умеет объединить:
10
10
 
@@ -15,9 +15,10 @@ import { Meta } from "@storybook/addon-docs/blocks";
15
15
  - состояние TanStack Query без полного query data;
16
16
  - технические сведения о persisted queries;
17
17
  - контекст React/runtime/transport;
18
- - lifecycle доставки: `pending`, `sending`, `sent`, `failed`.
18
+ - локальный draft lifecycle: `pending`, `sending`, `sent`, `failed`;
19
+ - durable queue с TTL, bounded capacity и атомарным межвкладочным lease.
19
20
 
20
- Важная граница: **lib-пакет не отправляет отчёт на сервер**. Он формирует draft и предоставляет точки подключения. Авторизация, HTTP/API delivery, retry, пользовательское согласие и UI принадлежат приложению либо API-пакету.
21
+ Важная граница: **lib-пакет не отправляет отчёт на сервер**. Он формирует draft, хранит queue и предоставляет атомарные переходы. Авторизация, HTTP/API delivery, retry, пользовательское согласие и UI принадлежат приложению либо API-пакету.
21
22
 
22
23
  ## Установка
23
24
 
@@ -26,15 +27,18 @@ import { Meta } from "@storybook/addon-docs/blocks";
26
27
  ```bash
27
28
  npm install @ryuzaki13/react-foundation-lib \
28
29
  @tanstack/react-query \
29
- @tanstack/query-persist-client-core
30
+ @tanstack/query-persist-client-core \
31
+ zod
30
32
  ```
31
33
 
32
34
  ```ts
33
35
  import {
34
36
  captureRuntimeErrorReport,
37
+ createErrorReportQueue,
35
38
  createErrorInfo,
36
39
  installErrorReportBrowserBreadcrumbs,
37
40
  setErrorReportRuntimeErrorReporter,
41
+ setErrorReportSanitizer,
38
42
  type ErrorReportDraft
39
43
  } from "@ryuzaki13/react-foundation-lib/error-report";
40
44
  ```
@@ -47,7 +51,8 @@ import {
47
51
  → нормализация + diagnostics + breadcrumbs
48
52
  → ErrorReportDraft(status = pending)
49
53
  → sessionStorage, максимум 10 drafts
50
- → app/API delivery
54
+ → enqueue payload в IndexedDB, максимум 50 записей / 7 суток
55
+ → app/API delivery через lease
51
56
  → updateErrorReportDraft(status = sending/sent/failed)
52
57
  ```
53
58
 
@@ -80,15 +85,15 @@ declare const __APP_ID__: string;
80
85
  declare const __APP_BUILD_ID__: string;
81
86
  ```
82
87
 
83
- `__APP_ID__` входит в ключи `sessionStorage`, а `__APP_BUILD_ID__` помогает связать ошибку с деплоем.
88
+ `__APP_ID__` входит в payload как `application` и в ключи `sessionStorage`, а `__APP_BUILD_ID__` помогает связать ошибку с деплоем. Приложение обязано использовать устойчивый deployment identifier, а не display name. Например, KTK-приложения используют `ru.education-system` и `ru.ktk-website`.
84
89
 
85
90
  Пример Vite:
86
91
 
87
92
  ```ts
88
93
  define: {
89
94
  __DEV__: JSON.stringify(mode !== "production"),
90
- __APP_ID__: JSON.stringify("education-arm"),
91
- __APP_BUILD_ID__: JSON.stringify(process.env.BUILD_ID ?? "local"),
95
+ __APP_ID__: JSON.stringify("ru.education-system"),
96
+ __APP_BUILD_ID__: JSON.stringify(process.env.GIT_COMMIT_SHA ?? "local"),
92
97
  }
93
98
  ```
94
99
 
@@ -165,7 +170,7 @@ type ErrorReportErrorInfo = {
165
170
  };
166
171
  ```
167
172
 
168
- Для обычного `Error` сохраняются `name`, непустой `message` и полный `stack`.
173
+ Для обычного `Error` сохраняются `name`, непустой `message` и `stack` до 64 KiB.
169
174
 
170
175
  ```ts
171
176
  createErrorInfo(new Error("Не удалось сохранить"));
@@ -189,16 +194,34 @@ createErrorInfo(new Error("Не удалось сохранить"));
189
194
  - максимальная глубина — 4;
190
195
  - максимум 20 элементов массива;
191
196
  - максимум 40 ключей объекта;
197
+ - строка — не более 4096 символов;
192
198
  - `undefined`, function, symbol и усечённая ветка получают специальные объекты `{ type: ... }`;
199
+ - циклическая ссылка получает `{ type: "circular" }`;
193
200
  - `bigint` превращается в строку;
194
201
  - `Date` — в ISO-строку.
202
+ - известные secret-like keys (`authorization`, `cookie`, `password`, `token`, `secret`, CSRF/API keys) получают `[REDACTED]`;
203
+ - email в свободной строке заменяется на `[REDACTED_EMAIL]`;
204
+ - значения полей с именем `*Url` сохраняются без search/hash.
195
205
 
196
206
  ```ts
197
207
  createDiagnosticValue({ retry: 2, handler: () => undefined });
198
208
  // { retry: 2, handler: { type: "function" } }
199
209
  ```
200
210
 
201
- Критически важно: функция **не скрывает значения по имени ключа**. Поля `token`, `password`, `authorization`, email и другие секреты сохранятся, если их передать. Это ограничитель размера и типа, а не security sanitizer.
211
+ Built-in redaction является страховочной сеткой и не распознаёт ФИО, телефоны и все предметные идентификаторы. Он не заменяет app-level allow-list.
212
+
213
+ ### `setErrorReportSanitizer(sanitizer?)`
214
+
215
+ Регистрирует единственный sanitizer приложения для error message/stack, delivery failure reason, query/mutation keys, meta, context, data shape и breadcrumb detail. Callback получает уже bounded `ErrorReportSafeValue` и scope, возвращает разрешённое значение либо `undefined`, чтобы удалить секцию. Результат повторно нормализуется foundation-ограничителями.
216
+
217
+ ```ts
218
+ setErrorReportSanitizer((value, context) => {
219
+ if (context.scope !== "context" || !value || Array.isArray(value) || typeof value !== "object") return value;
220
+ return "operation" in value ? { operation: value.operation } : undefined;
221
+ });
222
+ ```
223
+
224
+ Callback должен быть чистым и идемпотентным: capture может повторно применить его к уже подготовленной секции перед storage write. Если callback бросает исключение, рискованная секция удаляется. При teardown/hot reload вызовите `setErrorReportSanitizer(undefined)`.
202
225
 
203
226
  ### `sanitizeDetail(detail)`
204
227
 
@@ -260,7 +283,7 @@ getErrorReportBreadcrumbs();
260
283
  clearErrorReportBreadcrumbs();
261
284
  ```
262
285
 
263
- Возвращаемый массив является текущим внутренним массивом, а не глубокой копией. Считайте его readonly и не меняйте элементы вручную.
286
+ Возвращаемый массив является shallow copy. Вложенные элементы считайте readonly и не меняйте вручную.
264
287
 
265
288
  ### Автоматические browser breadcrumbs
266
289
 
@@ -276,9 +299,9 @@ uninstallBreadcrumbs();
276
299
  - click в capture phase;
277
300
  - `document.visibilitychange`.
278
301
 
279
- Для click сохраняются координаты/модификаторы и ограниченное описание meaningful target: tag, стабильные атрибуты, короткий текст и цепочка родителей. Глубина DOM-цепочки ограничена 20, текст — 80 символами, атрибут — 120.
302
+ Для click сохраняются координаты/модификаторы и ограниченное техническое описание meaningful target: tag, role, input type и цепочка родителей. Приложение может явно добавить slug-like `data-error-report-target`, например `student-save`. Глубина DOM-цепочки ограничена 20, атрибут — 120 символами.
280
303
 
281
- Код не читает `input.value` и не копирует CSS class list, но текст/ARIA/title/data-атрибуты тоже могут содержать чувствительные данные. Не помещайте секреты в DOM-атрибуты и UI-текст.
304
+ Код не читает `input.value`, DOM text, `id`, `title`, `aria-*`, CSS classes, `data-ui`, `data-action` и произвольные `data-*`. Значение `data-error-report-target` принимается только из латинских букв, цифр и символов `._:/-`; свободный текст в него помещать нельзя.
282
305
 
283
306
  На сервере установщик возвращает пустую cleanup-функцию.
284
307
 
@@ -288,7 +311,7 @@ uninstallBreadcrumbs();
288
311
 
289
312
  Собирает:
290
313
 
291
- - `queryHash` и диагностическое представление `queryKey`;
314
+ - безопасный hash уже очищенного query key и его диагностическое представление;
292
315
  - status/fetchStatus;
293
316
  - timestamps обновления data/error;
294
317
  - failure count, invalidation и observer count;
@@ -296,7 +319,7 @@ uninstallBreadcrumbs();
296
319
  - `dataShape` вместо data;
297
320
  - нормализованную query error.
298
321
 
299
- Значения `queryKey` и `meta` сохраняются. Если туда попал token или персональный идентификатор, он попадёт в отчёт. Проектируйте query keys как диагностически безопасный контракт.
322
+ Значения `queryKey` и `meta` проходят built-in redaction и app sanitizer. Предметные персональные идентификаторы всё равно должны удаляться policy приложения.
300
323
 
301
324
  ### `collectMutationDiagnostics(mutation)`
302
325
 
@@ -310,7 +333,7 @@ uninstallBreadcrumbs();
310
333
 
311
334
  Асинхронно читает IndexedDB persistence из `/query-client` и возвращает максимум 40 записей.
312
335
 
313
- Сохраняются storage key, buster, query hash/key, statuses, timestamps, failure count и error. `state.data` намеренно исключён полностью.
336
+ Сохраняются buster, безопасный hash/key, statuses, timestamps, failure count и error. IndexedDB storage key и `state.data` намеренно исключены полностью.
314
337
 
315
338
  Если IndexedDB/persistence недоступны или чтение бросает ошибку, возвращается пустой массив — сбор отчёта не падает.
316
339
 
@@ -349,7 +372,7 @@ const draft = await captureQueryErrorReport(error, query, queryClient, {
349
372
  const draft = await captureMutationErrorReport(error, mutation, queryClient, { source: "save-order", detail: { form: "order-edit" } });
350
373
  ```
351
374
 
352
- Текущая реализация включает `detail` в breadcrumb, но не переносит его в `payload.context` mutation draft. Если этот контекст критичен для доставки, добавьте безопасные данные в app-level envelope либо используйте явную согласованную модель до capture.
375
+ Текущая реализация включает очищенный `detail` и в breadcrumb, и в `payload.context` mutation draft.
353
376
 
354
377
  ## Capture runtime/React-ошибки
355
378
 
@@ -395,14 +418,96 @@ type ErrorReportDraft = {
395
418
 
396
419
  Payload содержит category/source/error, build environment, client, location, viewport, breadcrumbs и optional секции query/mutation/react/context.
397
420
 
421
+ `ErrorReportPayload` содержит обязательные `payloadVersion: 1` и `application`. Runtime parser принимает только текущую версию; unversioned и будущие формы не интерпретируются эвристически.
422
+
423
+ ### Runtime parsing
424
+
425
+ ```ts
426
+ parseErrorReportPayload(value);
427
+ parseErrorReportDraft(value);
428
+ parseErrorReportDrafts(value);
429
+ ```
430
+
431
+ Первые две функции возвращают текущий typed contract либо `undefined`. Parser массива независимо отбрасывает повреждённые элементы, сохраняя соседние валидные drafts. Дополнительно проверяется совпадение identity полей draft и payload и фактический размер payload.
432
+
433
+ ### Bounded payload
434
+
435
+ `limitErrorReportPayload(payload)` сначала повторно применяет sanitization, затем проверяет UTF-8 размер `JSON.stringify`. Предел — `ERROR_REPORT_PAYLOAD_MAX_BYTES` (256 KiB). При превышении optional diagnostics удаляются в фиксированном порядке, а `payload.truncation` фиксирует исходный размер, limit и список удалённых секций. `getErrorReportPayloadSize(payload)` возвращает фактический сериализованный размер в байтах.
436
+
398
437
  ### Хранение
399
438
 
400
- Drafts сохраняются под ключом `${__APP_ID__}.errorReport.drafts.v1`. В одной вкладке остаются последние 10 элементов. Старые удаляются с начала массива.
439
+ Versioned drafts сохраняются под ключом `${__APP_ID__}.errorReport.drafts.v2`. В одной вкладке остаются последние 10 элементов. Старые удаляются с начала массива. Предыдущая unversioned storage-форма не загружается.
401
440
 
402
441
  Storage errors и некорректный JSON подавляются. При переполнении sessionStorage текущее in-memory состояние обновится, но запись на диск может не сохраниться.
403
442
 
404
443
  Модуль загружает drafts при первом импорте. Это модульное состояние, общее для всех consumers одного bundle.
405
444
 
445
+ ## Durable IndexedDB queue
446
+
447
+ `sessionStorage` draft остаётся кратким диагностическим lifecycle текущей вкладки. Надёжную отложенную доставку обеспечивает отдельная queue:
448
+
449
+ ```ts
450
+ const queue = createErrorReportQueue();
451
+ if (!queue) {
452
+ // IndexedDB отсутствует: app фиксирует безопасную деградацию без рекурсии report flow.
453
+ return;
454
+ }
455
+
456
+ const draft = await captureRuntimeErrorReport(error, context, queryClient);
457
+ if (draft) {
458
+ await queue.enqueue(draft.payload);
459
+ }
460
+ ```
461
+
462
+ Создание возвращает `undefined`, когда IndexedDB недоступен. Ошибки открывания, quota и транзакций не поглощаются: coordinator обязан отделить storage failure от исходной пользовательской ошибки и применить безопасную recovery policy.
463
+
464
+ ### Storage schema и limits
465
+
466
+ - база: `${__APP_ID__}.error-report.queue.v1`;
467
+ - object store: `records`;
468
+ - атомарная envelope-запись: `queue.v1`;
469
+ - queue schema: `queueVersion: 1`;
470
+ - максимум: 50 записей;
471
+ - TTL каждой записи: 7 суток от `enqueue`, независимо от client `createdUtc`;
472
+ - lease по умолчанию: 30 секунд.
473
+
474
+ Одна bounded envelope используется намеренно: `createIndexedDbQueryStorage.updateItem` изменяет её в одной `readwrite` транзакции, поэтому две вкладки не могут одновременно получить один report. Payload уже ограничен 256 KiB, а число записей — 50; отдельная база не смешивается с persisted Query cache.
475
+
476
+ При заполнении удаляется самый старый отчёт без активного lease, чтобы сохранить более свежую диагностику. Активные leases не вытесняются; если все 50 записей leased, `enqueue` возвращает `capacity-exceeded`. Повторный `reportId` возвращает `duplicate` и не заменяет сохранённый payload.
477
+
478
+ ### Lease flow
479
+
480
+ ```ts
481
+ const lease = await queue.leaseNext({ ownerId: tabId });
482
+ if (!lease) return;
483
+
484
+ try {
485
+ await deliver(lease.payload);
486
+ await queue.acknowledge(lease.reportId, tabId);
487
+ } catch {
488
+ await queue.reschedule(lease.reportId, tabId, {
489
+ nextAttemptUtc,
490
+ failure: { utc: new Date().toISOString(), kind: "network" }
491
+ });
492
+ }
493
+ ```
494
+
495
+ `acknowledge`, `reschedule` и `discard` изменяют запись только при совпадении `ownerId`. После crash/закрытия вкладки lease автоматически снова становится доступным по `expiresUtc`. Если server принял отчёт, а acknowledgement потерялся, клиент повторит тот же `reportId`; backend обязан быть идемпотентным.
496
+
497
+ `lastFailure` хранит только технический класс, UTC и optional HTTP status. Текст transport error не попадает в durable storage.
498
+
499
+ ### Queue API
500
+
501
+ - `enqueue(payload, now?)` — runtime-валидирует payload, удаляет expired и добавляет запись;
502
+ - `leaseNext({ ownerId, now?, leaseDurationMs? })` — атомарно выбирает ближайшую due-запись;
503
+ - `acknowledge(reportId, ownerId)` — удаляет подтверждённую запись;
504
+ - `reschedule(reportId, ownerId, retry)` — снимает lease и задаёт следующую попытку;
505
+ - `discard(reportId, ownerId)` — удаляет terminal-запись;
506
+ - `getRecords(now?)` — bounded диагностический snapshot;
507
+ - `getNextWakeUtc(now?)` — ближайшее due/lease-expiry время для scheduler-а.
508
+
509
+ Queue не использует `navigator.onLine` как источник истины и не владеет timer/event lifecycle. Этим занимается delivery coordinator из `@ryuzaki13/react-foundation-api/error-report`.
510
+
406
511
  ## Низкоуровневый `captureErrorReportDraft(args)`
407
512
 
408
513
  Используйте, когда все diagnostic sections уже подготовлены вручную:
@@ -445,7 +550,7 @@ try {
445
550
  }
446
551
  ```
447
552
 
448
- Для неизвестного ID update возвращает `undefined`. Обновление поверхностное и принимает `Partial<ErrorReportDraft>`: библиотека не проверяет разрешённые переходы статусов. App delivery обязан не менять `reportId`, payload и другие identity-поля без необходимости.
553
+ Для неизвестного ID update возвращает `undefined`. Функция принимает только lifecycle-поля `status`, `sentUtc`, `failedReason`; identity и payload изменить через неё нельзя. Runtime parser повторно проверяет draft, а свободный текст причины ограничивается и очищается перед storage write. Семантику разрешённых переходов статусов задаст delivery coordinator.
449
554
 
450
555
  ## Environment API
451
556
 
@@ -486,7 +591,7 @@ setErrorReportTransportErrorReporter(async (error, context) => {
486
591
 
487
592
  ### `reportTransportError(error, context)`
488
593
 
489
- Context имеет source `fetch.parseResponse` или `fetch.buildHttpError` и может содержать URL, method, SAP client, status/content type, redirect/response type и безопасное summary HTML-response.
594
+ Context имеет source `fetch.parseResponse` или `fetch.buildHttpError` и может содержать URL, method, SAP client, status/content type, redirect/response type и только техническое summary HTML-response. HTML title и text preview больше не входят в контракт, потому что являются содержимым response body.
490
595
 
491
596
  Вызов планирует reporter через 250 мс. Задержка позволяет TanStack Query перехватить тот же `Error` и создать более полезный query/mutation report.
492
597
 
@@ -550,11 +655,10 @@ export function installErrorReporting(queryClient: QueryClient) {
550
655
  - queryKey и mutationKey;
551
656
  - query/mutation meta;
552
657
  - `context.detail`;
553
- - URL и pathname;
658
+ - pathname без search/hash;
554
659
  - error message/stack;
555
- - UI text, ARIA/title/data attributes breadcrumbs;
660
+ - app-provided context, если policy ошибочно разрешила предметные данные;
556
661
  - user agent, language, timezone;
557
- - имена persisted storage keys.
558
662
 
559
663
  Поэтому:
560
664
 
@@ -570,11 +674,11 @@ export function installErrorReporting(queryClient: QueryClient) {
570
674
 
571
675
  ### Ожидать автоматическую отправку
572
676
 
573
- Capture только создаёт draft. Delivery нужно явно подключить.
677
+ Capture только создаёт draft. Приложение должно enqueue-ить его payload и один раз запустить API coordinator в client lifecycle.
574
678
 
575
- ### Использовать `createDiagnosticValue` как redactor
679
+ ### Считать built-in redaction полной privacy policy
576
680
 
577
- Он ограничивает размер, но сохраняет значения независимо от имени поля.
681
+ Foundation удаляет известные secret-like keys и email, но не умеет надёжно распознавать ФИО, телефоны и предметные IDs. Настройте app sanitizer по allow-list.
578
682
 
579
683
  ### Вызывать production capture в development и ждать draft
580
684
 
@@ -602,7 +706,9 @@ Capture только создаёт draft. Delivery нужно явно подк
602
706
  | ---------------- | --------------------------------------------------------------------------------------------------------------------------------- |
603
707
  | Capture | `captureQueryErrorReport`, `captureMutationErrorReport`, `captureRuntimeErrorReport`, `captureErrorReportDraft` |
604
708
  | Errors | `createErrorInfo` |
605
- | Safe values | `createDiagnosticValue`, `createDataShape`, `sanitizeDetail` |
709
+ | Safe values | `createDiagnosticValue`, `createDataShape`, `sanitizeDetail`, `sanitizeErrorReportValue`, `setErrorReportSanitizer` |
710
+ | Payload contract | `ERROR_REPORT_PAYLOAD_VERSION`, `ERROR_REPORT_PAYLOAD_MAX_BYTES`, `limitErrorReportPayload`, `getErrorReportPayloadSize`, parsers |
711
+ | Durable queue | `createErrorReportQueue`, queue limits, record/parser и atomic lease transitions |
606
712
  | Breadcrumbs | `addErrorReportBreadcrumb`, `getErrorReportBreadcrumbs`, `clearErrorReportBreadcrumbs`, `installErrorReportBrowserBreadcrumbs` |
607
713
  | Diagnostics | `collectQueryDiagnostics`, `collectMutationDiagnostics`, `collectQueryClientDiagnostics`, `collectPersistedQueryDiagnostics` |
608
714
  | Drafts | `getErrorReportDraft`, `getErrorReportDrafts`, `updateErrorReportDraft` |