import{n as e}from"./rolldown-runtime-C0FnF6B9.js";import{n as t}from"./iframe-GrlyTB2d.js";import{i as n,r}from"./react-qN2cStNd.js";import{l as i,m as a}from"./blocks-DkLPipTQ.js";function o(e){let t={code:`code`,h1:`h1`,h2:`h2`,h3:`h3`,li:`li`,ol:`ol`,p:`p`,pre:`pre`,strong:`strong`,table:`table`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,...n(),...e.components};return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(i,{title:`Foundation Lib/Runtime, state и diagnostics`}),`
`,(0,c.jsx)(t.h1,{id:`runtime-state-и-diagnostics`,children:`Runtime, state и diagnostics`}),`
`,(0,c.jsx)(t.p,{children:`Этот раздел содержит инфраструктурные API, которые создаются в composition root host-приложения. Они не должны подключаться из каждой feature: persistence, BroadcastChannel, notifications и error reporting имеют глобальный lifecycle и приватные данные.`}),`
`,(0,c.jsxs)(t.h2,{id:`query-client-tanstack-query-инфраструктура`,children:[(0,c.jsx)(t.code,{children:`query-client`}),`: TanStack Query инфраструктура`]}),`
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import {
	configureQueryPersistenceProjectAdapter,
	createQueryClient,
	installReactQueryBroadcast,
	installSessionScopedQueryReset,
	persistedQueryMeta,
	resetSessionScopedQueries,
	sessionScopedQueryMeta
} from "@ryuzaki13/react-foundation-lib/query-client";
`})}),`
`,(0,c.jsx)(t.h3,{id:`composition-root`,children:`Composition root`}),`
`,(0,c.jsxs)(t.ol,{children:[`
`,(0,c.jsxs)(t.li,{children:[`До создания `,(0,c.jsx)(t.code,{children:`QueryClient`}),` вызовите `,(0,c.jsx)(t.code,{children:`configureQueryPersistenceProjectAdapter`}),` при необходимости задать application-specific `,(0,c.jsx)(t.code,{children:`persistenceBuster`}),` и `,(0,c.jsx)(t.code,{children:`resolveSystemIdentifier`}),`.`]}),`
`,(0,c.jsxs)(t.li,{children:[`Создайте один `,(0,c.jsx)(t.code,{children:`QueryClient`}),` через `,(0,c.jsx)(t.code,{children:`createQueryClient({ onQueryError?, onMutationError? })`}),`.`]}),`
`,(0,c.jsxs)(t.li,{children:[`В browser bootstrap установите sync/reset channels и обязательно сохраните их `,(0,c.jsx)(t.code,{children:`cleanup`}),` для shutdown/tests.`]}),`
`]}),`
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`configureQueryPersistenceProjectAdapter({
	persistenceBuster: "host-v1",
	resolveSystemIdentifier: () => "host-production"
});

const queryClient = createQueryClient({
	onQueryError: (error, query) => reportQueryError(error, query)
});

const broadcast = installReactQueryBroadcast(queryClient, {
	queriesChannel: "host-query-cache",
	eventsChannel: "host-query-events"
});
const sessionReset = installSessionScopedQueryReset(queryClient, {
	channelName: "host-session-reset"
});

// teardown: broadcast.cleanup(); sessionReset.cleanup();
`})}),`
`,(0,c.jsx)(t.h3,{id:`persistence-session-scope-и-broadcast`,children:`Persistence, session scope и broadcast`}),`
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`API`}),(0,c.jsx)(t.th,{children:`Когда использовать`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`persistedQueryMeta`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`meta`}),` query только для публичных, serializable данных, которым разрешено переживать restart.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`sessionScopedQueryMeta`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`meta`}),` query, чей результат зависит от текущей server session/role. Такой query никогда не сохраняется.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`shouldPersistQuery(query)`})}),(0,c.jsxs)(t.td,{children:[`Predicate persistence: true лишь для `,(0,c.jsx)(t.code,{children:`persist: true`}),` без `,(0,c.jsx)(t.code,{children:`sessionScoped`}),`.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`createIndexedDbQueryStorage(options?)`})}),(0,c.jsxs)(t.td,{children:[`Создаёт async IndexedDB storage либо `,(0,c.jsx)(t.code,{children:`undefined`}),`, если API недоступен.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`createReactQueryPersister()`})}),(0,c.jsxs)(t.td,{children:[`Создаёт TanStack persister с 90-дневным max age или `,(0,c.jsx)(t.code,{children:`undefined`}),`.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`installReactQueryBroadcast(queryClient, channels)`})}),(0,c.jsxs)(t.td,{children:[`Синхронизирует Query cache и explicit invalidate/set events между вкладками; возвращает `,(0,c.jsx)(t.code,{children:`{ broadcast, cleanup }`}),`.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`broadcastCacheEvent(event)`}),` / `,(0,c.jsx)(t.code,{children:`setBroadcastFn(fn)`})]}),(0,c.jsx)(t.td,{children:`Imperative bridge для explicit cache event. Настраивается один раз в composition root.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`resetSessionScopedQueries(queryClient, { broadcast? })`})}),(0,c.jsx)(t.td,{children:`Отменяет, очищает и refetches active session-scoped queries после логина, логаута или смены роли.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`installSessionScopedQueryReset(...)`})}),(0,c.jsx)(t.td,{children:`Рассылает только команду reset, не query data; возвращает cleanup.`})]})]})]}),`
`,(0,c.jsx)(t.p,{children:`Не отмечайте query обоими meta-флагами. Не кладите в persisted/broadcast cache tokens, персональные данные без принятой политики, DOM nodes, функции или class instances.`}),`
`,(0,c.jsx)(t.h3,{id:`оптимистичная-мутация`,children:`Оптимистичная мутация`}),`
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`onMutateOptimistic`}),`, `,(0,c.jsx)(t.code,{children:`onErrorOptimistic`}),`, `,(0,c.jsx)(t.code,{children:`onSuccessOptimistic`}),`, `,(0,c.jsx)(t.code,{children:`onSettledOptimistic`}),` — low-level helpers для одного известного cache key. `,(0,c.jsx)(t.code,{children:`onMutateOptimistic`}),` сначала отменяет запросы, сохраняет `,(0,c.jsx)(t.code,{children:`previous`}),`, затем применяет patch. Контекст обязательно передаётся в `,(0,c.jsx)(t.code,{children:`onError`}),` для rollback.`]}),`
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`useMutation({
	mutationFn: updateTodo,
	onMutate: (input) => onMutateOptimistic(queryClient, todoKey, (old) => patchTodo(old, input)),
	onError: (_error, _input, context) => onErrorOptimistic(queryClient, todoKey, context),
	onSettled: () => onSettledOptimistic(queryClient, todoKey, { refetchType: "active" })
});
`})}),`
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`OnSettledOptimisticOptions.refetchType`}),` defaults to `,(0,c.jsx)(t.code,{children:`"none"`}),`. Не применяйте helper к непонятно широкому key: cache strategy должен владеть entity/feature, а не shared infrastructure.`]}),`
`,(0,c.jsxs)(t.h2,{id:`error-report-безопасный-diagnostic-draft`,children:[(0,c.jsx)(t.code,{children:`error-report`}),`: безопасный diagnostic draft`]}),`
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import {
	captureQueryErrorReport,
	captureRuntimeErrorReport,
	getErrorReportDrafts,
	installErrorReportBrowserBreadcrumbs,
	setErrorReportRuntimeErrorReporter,
	setErrorReportTransportErrorReporter
} from "@ryuzaki13/react-foundation-lib/error-report";
`})}),`
`,(0,c.jsxs)(t.p,{children:[`Пакет подготавливает, sanitizes и локально сохраняет diagnostic draft. Он `,(0,c.jsx)(t.strong,{children:`не`}),` отправляет отчёт в backend сам: transport/delivery принадлежит соответствующему API-пакету или host integration.`]}),`
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Группа API`}),(0,c.jsx)(t.th,{children:`Контракт`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Environment`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`isErrorReportingEnabled`}),`, `,(0,c.jsx)(t.code,{children:`getErrorReportEnvironment`}),`, `,(0,c.jsx)(t.code,{children:`getErrorReportClientEnvironment`}),` дают browser/non-dev gate и минимальный runtime context.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Safe values`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`createDiagnosticValue`}),`, `,(0,c.jsx)(t.code,{children:`createDataShape`}),`, `,(0,c.jsx)(t.code,{children:`sanitizeDetail`}),`, `,(0,c.jsx)(t.code,{children:`createErrorInfo`}),` превращают `,(0,c.jsx)(t.code,{children:`unknown`}),` в serializable diagnostic form. Не передавайте секреты даже в sanitizing API.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Breadcrumbs`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`addErrorReportBreadcrumb`}),`, `,(0,c.jsx)(t.code,{children:`getErrorReportBreadcrumbs`}),`, `,(0,c.jsx)(t.code,{children:`clearErrorReportBreadcrumbs`}),`, `,(0,c.jsx)(t.code,{children:`installErrorReportBrowserBreadcrumbs`}),`. Установка browser listener возвращает cleanup.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Capture`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`captureQueryErrorReport`}),`, `,(0,c.jsx)(t.code,{children:`captureMutationErrorReport`}),`, `,(0,c.jsx)(t.code,{children:`captureRuntimeErrorReport`}),` создают `,(0,c.jsx)(t.code,{children:`ErrorReportDraft`}),` с query/mutation/runtime diagnostics.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Drafts`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`captureErrorReportDraft`}),`, `,(0,c.jsx)(t.code,{children:`getErrorReportDraft`}),`, `,(0,c.jsx)(t.code,{children:`getErrorReportDrafts`}),`, `,(0,c.jsx)(t.code,{children:`updateErrorReportDraft`}),` управляют локальными draft.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Reporting bridge`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`setErrorReportRuntimeErrorReporter`}),` + `,(0,c.jsx)(t.code,{children:`reportRuntimeError`}),`; `,(0,c.jsx)(t.code,{children:`setErrorReportTransportErrorReporter`}),`, `,(0,c.jsx)(t.code,{children:`reportTransportError`}),`, `,(0,c.jsx)(t.code,{children:`suppressTransportErrorReport`}),`, `,(0,c.jsx)(t.code,{children:`isTransportErrorReportScheduled`}),`.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Diagnostics`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`collectQueryDiagnostics`}),`, `,(0,c.jsx)(t.code,{children:`collectMutationDiagnostics`}),`, `,(0,c.jsx)(t.code,{children:`collectQueryClientDiagnostics`}),`, `,(0,c.jsx)(t.code,{children:`collectPersistedQueryDiagnostics`}),`.`]})]})]})]}),`
`,(0,c.jsx)(t.h3,{id:`подключение-reporter-а`,children:`Подключение reporter-а`}),`
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`setErrorReportRuntimeErrorReporter(async (error, context) => {
	const draft = captureRuntimeErrorReport(error, context, queryClient);
	// Передайте draft в owner transport только после собственной privacy policy.
	await deliverDiagnosticDraft(await draft);
});
`})}),`
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`ErrorReportPayload`}),`, `,(0,c.jsx)(t.code,{children:`ErrorReportDraft`}),`, `,(0,c.jsx)(t.code,{children:`ErrorReportBreadcrumb`}),`, `,(0,c.jsx)(t.code,{children:`ErrorReportErrorInfo`}),`, `,(0,c.jsx)(t.code,{children:`ErrorReportSafeValue`}),` и context types — публичные contracts для typed delivery. Runtime capture в браузере coalesces одинаковые ошибки на коротком окне, чтобы flood не создавал много draft. Не дублируйте transport report: `,(0,c.jsx)(t.code,{children:`capture*`}),` корректно вызывает `,(0,c.jsx)(t.code,{children:`suppressTransportErrorReport`}),` для исходной ошибки.`]}),`
`,(0,c.jsxs)(t.h2,{id:`notifications-global-imperative-facade`,children:[(0,c.jsx)(t.code,{children:`notifications`}),`: global imperative facade`]}),`
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { bindNotifications, createNotificationsStore, notify } from "@ryuzaki13/react-foundation-lib/notifications";
`})}),`
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const notificationsStore = createNotificationsStore();
const unbind = bindNotifications(notificationsStore);

notify.success("Сохранено");
const progress = notify.progress("Загрузка…");
progress.success("Готово");

// teardown composition root: unbind();
`})}),`
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`API`}),(0,c.jsx)(t.th,{children:`Назначение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`createNotificationsStore()`})}),(0,c.jsx)(t.td,{children:`Создаёт Zustand store с max 6 items и timer cleanup. Вызов только в browser-compatible UI composition.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`bindNotifications(store)`})}),(0,c.jsxs)(t.td,{children:[`Связывает единственный store с imperative `,(0,c.jsx)(t.code,{children:`notify`}),`; возвращает unbind. До bind любой `,(0,c.jsx)(t.code,{children:`notify.*`}),` бросает Error.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`notify.push/upsert/update/dismiss/clear`})}),(0,c.jsxs)(t.td,{children:[`Imperative actions над `,(0,c.jsx)(t.code,{children:`NotificationPushInput`}),`/`,(0,c.jsx)(t.code,{children:`NotificationUpdatePatch`}),`.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`notify.success/info/warning/error`})}),(0,c.jsx)(t.td,{children:`Удобные typed shortcuts.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`notify.progress(message, opts?)`})}),(0,c.jsxs)(t.td,{children:[`Возвращает `,(0,c.jsx)(t.code,{children:`{ id, update, success, error, dismiss }`}),` для долгой операции.`]})]})]})]}),`
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`Notification`}),`, `,(0,c.jsx)(t.code,{children:`NotificationAction`}),`, `,(0,c.jsx)(t.code,{children:`NotificationType`}),`, `,(0,c.jsx)(t.code,{children:`NotificationTone`}),`, `,(0,c.jsx)(t.code,{children:`NotificationId`}),` описывают данные, а не UI rendering. UI owner подписывается на `,(0,c.jsx)(t.code,{children:`NotificationsStoreApi`}),` и отвечает за accessibility/визуал.`]}),`
`,(0,c.jsx)(t.h2,{id:`odata-collection-preferences-session-и-fingerprint`,children:`OData collection preferences, session и fingerprint`}),`
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { odataCollectionConfig, useODataCollectionStore } from "@ryuzaki13/react-foundation-lib/odata";
import { getSessionStorageId } from "@ryuzaki13/react-foundation-lib/session-storage";
import { getClientFingerprint } from "@ryuzaki13/react-foundation-lib/fingerprint";
`})}),`
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`odataCollectionConfig`}),` даёт selectors для общих UI-defaults (`,(0,c.jsx)(t.code,{children:`maxVisibleItems`}),`, min search lengths, debounce); `,(0,c.jsx)(t.code,{children:`useODataCollectionStore`}),` даёт state и setters. Это клиентские предпочтения, не backend data и не metadata cache.`]}),`
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`getSessionStorageId(key, createId?)`}),` возвращает стабильный ID одной вкладки из `,(0,c.jsx)(t.code,{children:`sessionStorage`}),`; при storage failure всё равно вернёт новый ID. `,(0,c.jsx)(t.code,{children:`getClientFingerprint()`}),` асинхронно возвращает fingerprint или `,(0,c.jsx)(t.code,{children:`null`}),`, если библиотека/API недоступны. Не используйте fingerprint как authentication/authorization credential.`]})]})}function s(e={}){let{wrapper:t}={...n(),...e.components};return t?(0,c.jsx)(t,{...e,children:(0,c.jsx)(o,{...e})}):o(e)}var c;function l(){return(l=e((()=>{c=t(),r(),a()})))()}l();export{s as default};