@ryuzaki13/react-foundation-lib 1.8.3 → 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.
- package/README.md +5 -3
- package/dist/error-report/breadcrumbs.d.ts.map +1 -1
- package/dist/error-report/capture.d.ts.map +1 -1
- package/dist/error-report/diagnostics.d.ts.map +1 -1
- package/dist/error-report/drafts.d.ts +2 -11
- package/dist/error-report/drafts.d.ts.map +1 -1
- package/dist/error-report/errorInfo.d.ts.map +1 -1
- package/dist/error-report/index.d.ts +5 -0
- package/dist/error-report/index.d.ts.map +1 -1
- package/dist/error-report/index.js +920 -210
- package/dist/error-report/index.js.map +1 -1
- package/dist/error-report/payload.d.ts +13 -0
- package/dist/error-report/payload.d.ts.map +1 -0
- package/dist/error-report/queue.d.ts +15 -0
- package/dist/error-report/queue.d.ts.map +1 -0
- package/dist/error-report/queueContract.d.ts +62 -0
- package/dist/error-report/queueContract.d.ts.map +1 -0
- package/dist/error-report/queueRecord.d.ts +9 -0
- package/dist/error-report/queueRecord.d.ts.map +1 -0
- package/dist/error-report/safeValue.d.ts +26 -2
- package/dist/error-report/safeValue.d.ts.map +1 -1
- package/dist/error-report/schema.d.ts +14 -0
- package/dist/error-report/schema.d.ts.map +1 -0
- package/dist/error-report/transport.d.ts +0 -2
- package/dist/error-report/transport.d.ts.map +1 -1
- package/dist/error-report/types.d.ts +26 -1
- package/dist/error-report/types.d.ts.map +1 -1
- package/dist/pwa/index.d.ts +1 -0
- package/dist/pwa/index.d.ts.map +1 -1
- package/dist/pwa/index.js +75 -59
- package/dist/pwa/index.js.map +1 -1
- package/dist/pwa/useBrowserOnlineStatus.d.ts +3 -0
- package/dist/pwa/useBrowserOnlineStatus.d.ts.map +1 -0
- package/dist/query-client/index.js +137 -43
- package/dist/query-client/index.js.map +1 -1
- package/dist/query-client/persistence.d.ts +21 -3
- package/dist/query-client/persistence.d.ts.map +1 -1
- package/dist/query-client/queryMeta.d.ts +21 -2
- package/dist/query-client/queryMeta.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/error-report/README.mdx +133 -27
- package/src/pwa/README.mdx +4 -0
- package/src/query-client/README.mdx +48 -7
|
@@ -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` собирает клиентскую диагностику ошибки в
|
|
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
|
|
18
|
+
- локальный draft lifecycle: `pending`, `sending`, `sent`, `failed`;
|
|
19
|
+
- durable queue с TTL, bounded capacity и атомарным межвкладочным lease.
|
|
19
20
|
|
|
20
|
-
Важная граница: **lib-пакет не отправляет отчёт на сервер**. Он формирует draft и предоставляет
|
|
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
|
-
→
|
|
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-
|
|
91
|
-
__APP_BUILD_ID__: JSON.stringify(process.env.
|
|
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` и
|
|
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
|
-
|
|
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
|
-
Возвращаемый массив является
|
|
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,
|
|
302
|
+
Для click сохраняются координаты/модификаторы и ограниченное техническое описание meaningful target: tag, role, input type и цепочка родителей. Приложение может явно добавить slug-like `data-error-report-target`, например `student-save`. Глубина DOM-цепочки ограничена 20, атрибут — 120 символами.
|
|
280
303
|
|
|
281
|
-
Код не читает `input.value`
|
|
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
|
-
-
|
|
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`
|
|
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
|
-
Сохраняются
|
|
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,
|
|
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
|
-
|
|
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`.
|
|
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 и
|
|
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
|
-
-
|
|
658
|
+
- pathname без search/hash;
|
|
554
659
|
- error message/stack;
|
|
555
|
-
-
|
|
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.
|
|
677
|
+
Capture только создаёт draft. Приложение должно enqueue-ить его payload и один раз запустить API coordinator в client lifecycle.
|
|
574
678
|
|
|
575
|
-
###
|
|
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` |
|
package/src/pwa/README.mdx
CHANGED
|
@@ -13,6 +13,9 @@ import { Meta } from "@storybook/addon-docs/blocks";
|
|
|
13
13
|
|
|
14
14
|
Используйте точный subpath. Внутренний файл `serviceWorker` не является самостоятельным публичным export, даже если отдельные функции используются самим hook.
|
|
15
15
|
|
|
16
|
+
`useBrowserOnlineStatus()` возвращает browser hint `navigator.onLine` через `useSyncExternalStore` и подписывается на события
|
|
17
|
+
`online`/`offline`. Это сигнал для UX и запуска revalidation, а не доказательство доступности конкретного backend.
|
|
18
|
+
|
|
16
19
|
## Часть 1. Обновление Service Worker
|
|
17
20
|
|
|
18
21
|
### Задача hook
|
|
@@ -378,6 +381,7 @@ Waiting worker остаётся установленным. Следующая
|
|
|
378
381
|
| Экспорт | Публичный path | Назначение |
|
|
379
382
|
| ----------------------------------- | -------------- | -------------------------------------------------- |
|
|
380
383
|
| `useServiceWorkerUpdate()` | `/pwa` | Регистрация, проверка и UI-состояние обновления SW |
|
|
384
|
+
| `useBrowserOnlineStatus()` | `/pwa` | Реактивный browser online/offline hint |
|
|
381
385
|
| `parseSwCachePolicy()` | оба | Разобрать текстовую политику |
|
|
382
386
|
| `buildSwCachePolicyValue()` | оба | Собрать каноническую строку из draft |
|
|
383
387
|
| `normalizeSwCacheName()` | оба | Подготовить безопасную часть cache name |
|
|
@@ -274,12 +274,14 @@ Stale не означает «удалена». Data может продолжа
|
|
|
274
274
|
| Нужны данные только пока открыта вкладка? | Обычная query без `persistedQueryMeta`. |
|
|
275
275
|
| Данные публичные, тяжёлые и могут пережить reload? | Рассмотреть `persistedQueryMeta`. |
|
|
276
276
|
| Данные зависят от пользователя, роли или server session? | `sessionScopedQueryMeta`. |
|
|
277
|
+
| Session snapshot явно разрешено читать offline? | `createSessionQueryPersistenceMeta` + active scope. |
|
|
277
278
|
| Нужно передать обновление другой открытой вкладке? | Broadcast installation и подходящее cache event. |
|
|
278
279
|
| UI должен измениться до ответа mutation? | Optimistic update с обязательным rollback policy. |
|
|
279
280
|
| Server response авторитетен и уже содержит новое состояние? | `setQueryData`/`onSuccessOptimistic`. |
|
|
280
281
|
| После mutation нужен свежий server snapshot? | Scoped `invalidateQueries` с выбранным `refetchType`. |
|
|
281
282
|
|
|
282
|
-
`persistedQueryMeta` и `sessionScopedQueryMeta` взаимоисключающие.
|
|
283
|
+
`persistedQueryMeta` и `sessionScopedQueryMeta` взаимоисключающие. Для узкого offline allowlist session data используется
|
|
284
|
+
отдельный `sessionPersistence` contract: он требует активную server-session identity и абсолютный `expiresAt`.
|
|
283
285
|
|
|
284
286
|
## Быстрый старт
|
|
285
287
|
|
|
@@ -489,7 +491,8 @@ queryOptions({
|
|
|
489
491
|
});
|
|
490
492
|
```
|
|
491
493
|
|
|
492
|
-
`Record<string, unknown>` разрешает consumer-проекту добавлять собственную metadata. Infrastructure этого модуля читает
|
|
494
|
+
`Record<string, unknown>` разрешает consumer-проекту добавлять собственную metadata. Infrastructure этого модуля читает
|
|
495
|
+
`persist`, `sessionScoped` и явный `sessionPersistence` contract.
|
|
493
496
|
|
|
494
497
|
### `persistedQueryMeta`
|
|
495
498
|
|
|
@@ -533,6 +536,36 @@ queryOptions({
|
|
|
533
536
|
|
|
534
537
|
При смене сессии host обязан вызвать `resetSessionScopedQueries`.
|
|
535
538
|
|
|
539
|
+
### Session-scoped offline persistence
|
|
540
|
+
|
|
541
|
+
Только для явно согласованного offline allowlist создайте bounded metadata:
|
|
542
|
+
|
|
543
|
+
```ts
|
|
544
|
+
import {
|
|
545
|
+
createSessionQueryPersistenceMeta,
|
|
546
|
+
setSessionQueryPersistenceScope
|
|
547
|
+
} from "@ryuzaki13/react-foundation-lib/query-client";
|
|
548
|
+
|
|
549
|
+
await setSessionQueryPersistenceScope({
|
|
550
|
+
id: session.id,
|
|
551
|
+
expiresAt: session.expiresAt
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
queryOptions({
|
|
555
|
+
queryKey: ["opened-week", weekStart],
|
|
556
|
+
queryFn: fetchWeek,
|
|
557
|
+
meta: createSessionQueryPersistenceMeta({
|
|
558
|
+
cacheName: "opened-weeks",
|
|
559
|
+
maxEntries: 24
|
|
560
|
+
}),
|
|
561
|
+
networkMode: "offlineFirst"
|
|
562
|
+
});
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
Scope устанавливается отдельно от query contract, не содержит cookie secret/access token и меняет in-memory границу до очистки IndexedDB.
|
|
566
|
+
При `null`, истёкшем `expiresAt` или смене `id` старые session snapshots удаляются. `networkMode: "offlineFirst"` нужен, чтобы
|
|
567
|
+
TanStack вызвал persister до network pause. Cached data остаётся неавторитетным: write после reconnect обязан повторно получить серверный state.
|
|
568
|
+
|
|
536
569
|
### Meta нельзя совмещать
|
|
537
570
|
|
|
538
571
|
Неверно:
|
|
@@ -550,13 +583,14 @@ meta: {
|
|
|
550
583
|
|
|
551
584
|
- публичный/безопасный долгоживущий справочник → `persistedQueryMeta`;
|
|
552
585
|
- auth/session dependent data → `sessionScopedQueryMeta`;
|
|
586
|
+
- разрешённый bounded offline snapshot → `createSessionQueryPersistenceMeta`;
|
|
553
587
|
- обычные transient data → meta не нужна.
|
|
554
588
|
|
|
555
589
|
## Настройка project adapter
|
|
556
590
|
|
|
557
591
|
### Зачем нужен adapter
|
|
558
592
|
|
|
559
|
-
Foundation-пакет не знает имя SAP system, client, tenant или окружение конкретного приложения. `QueryPersistenceProjectAdapter` позволяет host передать устойчивый namespace и собственный buster:
|
|
593
|
+
Foundation-пакет не знает имя SAP system, client, tenant или окружение конкретного приложения. `QueryPersistenceProjectAdapter` позволяет host передать устойчивый namespace и собственный buster. Текущая session identity задаётся отдельным runtime API и не входит в adapter:
|
|
560
594
|
|
|
561
595
|
```ts
|
|
562
596
|
type QueryPersistenceProjectAdapter = {
|
|
@@ -778,7 +812,8 @@ Storage не скрывает ошибки. Promise отклоняется, ес
|
|
|
778
812
|
const persister = createReactQueryPersister();
|
|
779
813
|
```
|
|
780
814
|
|
|
781
|
-
Возвращает
|
|
815
|
+
Возвращает routing-обёртку над `experimental_createQueryPersister` либо `undefined`, если IndexedDB недоступен. Обычные public query
|
|
816
|
+
идут в прежний persister, а query с `sessionPersistence` — в отдельный cache активной сессии с независимым `maxEntries`.
|
|
782
817
|
|
|
783
818
|
Настройки foundation wrapper:
|
|
784
819
|
|
|
@@ -2356,9 +2391,11 @@ Profile tests подтверждают:
|
|
|
2356
2391
|
|
|
2357
2392
|
| Symbol | Вид | Назначение |
|
|
2358
2393
|
| ------------------------ | ----- | ----------------------------------------------- |
|
|
2359
|
-
| `AppQueryMeta`
|
|
2360
|
-
| `
|
|
2361
|
-
| `
|
|
2394
|
+
| `AppQueryMeta` | type | Public/session persistence и consumer metadata. |
|
|
2395
|
+
| `SessionQueryPersistencePolicy` | type | Имя и bounded limit session cache. |
|
|
2396
|
+
| `persistedQueryMeta` | const | Opt-in public persistence marker. |
|
|
2397
|
+
| `sessionScopedQueryMeta` | const | Session reset marker без browser persistence. |
|
|
2398
|
+
| `createSessionQueryPersistenceMeta` | function | Явный opt-in bounded persistence текущей server session. |
|
|
2362
2399
|
|
|
2363
2400
|
### Persistence project adapter
|
|
2364
2401
|
|
|
@@ -2377,6 +2414,10 @@ Profile tests подтверждают:
|
|
|
2377
2414
|
| `shouldPersistQuery` | function | Opt-in predicate с session safety. |
|
|
2378
2415
|
| `createIndexedDbQueryStorage` | function | AsyncStorage поверх IndexedDB. |
|
|
2379
2416
|
| `createReactQueryPersister` | function | Настроенный per-query TanStack persister. |
|
|
2417
|
+
| `SessionQueryPersistenceScope` | type | Public session id и абсолютный expiresAt. |
|
|
2418
|
+
| `getSessionQueryPersistenceScope` | function | Читает валидный активный browser scope. |
|
|
2419
|
+
| `setSessionQueryPersistenceScope` | function | Меняет scope и очищает чужие snapshots. |
|
|
2420
|
+
| `clearSessionQueryPersistence` | function | Удаляет один или все session snapshots. |
|
|
2380
2421
|
|
|
2381
2422
|
Options type `createIndexedDbQueryStorage` не экспортируется отдельным symbol; используйте структурный object или `Parameters<typeof createIndexedDbQueryStorage>[0]`.
|
|
2382
2423
|
|