@ryuzaki13/react-foundation-lib 2.3.1 → 2.5.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 CHANGED
@@ -618,9 +618,9 @@ Pipeline — runtime форматирования ячейки. Он прини
618
618
 
619
619
  | API | Поведение |
620
620
  | -------------------------- | ------------------------------------------------------------------------------------------------- |
621
- | `createNotificationsStore` | Zustand vanilla store, максимум 6 уведомлений, TTL timers, `push/update/upsert/dismiss/clear`. |
622
- | `bindNotifications` | Привязка store к imperative `notify`. |
623
- | `notify` | `push/upsert/update/dismiss/clear`, short methods `success/info/warning/error`, progress pattern. |
621
+ | `createNotificationsStore` | Zustand store: до 6 активных toast, полная session-history и TTL timers. |
622
+ | `bindNotifications` | Привязка store к imperative `notify`. |
623
+ | `notify` | `push/upsert/update/dismiss/clear/clearHistory`, short methods и progress pattern. |
624
624
 
625
625
  `notify` требует bound store; без provider будет ошибка.
626
626
 
@@ -11,6 +11,7 @@ var t = null, n = (e) => (t = e, () => {
11
11
  update: (e, t) => r().getState().actions.update(e, t),
12
12
  dismiss: (e) => r().getState().actions.dismiss(e),
13
13
  clear: () => r().getState().actions.clear(),
14
+ clearHistory: () => r().getState().actions.clearHistory(),
14
15
  success: (e, t) => r().getState().actions.push({
15
16
  type: "success",
16
17
  message: e,
@@ -64,7 +65,8 @@ var t = null, n = (e) => (t = e, () => {
64
65
  dismiss: () => i.dismiss(o)
65
66
  };
66
67
  }
67
- }), a = () => `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`, o = 6, s = () => {
68
+ }), a = () => `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`, o = 6;
69
+ function s() {
68
70
  let t = /* @__PURE__ */ new Map(), n = (e) => {
69
71
  let n = t.get(e);
70
72
  n && (window.clearTimeout(n), t.delete(e));
@@ -76,30 +78,39 @@ var t = null, n = (e) => (t = e, () => {
76
78
  t.set(r, a);
77
79
  }, i = e()((e, s) => ({
78
80
  items: [],
81
+ history: [],
79
82
  actions: {
80
83
  push: (t) => {
81
- let n = t.id ?? a(), s = Date.now(), c = {
82
- id: n,
84
+ let c = t.id ?? a(), l = Date.now(), u = {
85
+ id: c,
83
86
  type: t.type,
84
87
  title: t.title,
85
88
  message: t.message,
86
- createdAt: s,
89
+ createdAt: l,
87
90
  ttlMs: t.ttlMs,
88
91
  dismissible: t.dismissible ?? !0,
89
92
  actions: t.actions
90
- };
91
- return e((e) => ({ items: [c, ...e.items.filter((e) => e.id !== n)].slice(0, o) })), r(i, n, c.ttlMs), n;
93
+ }, d = s().items, f = [u, ...d.filter((e) => e.id !== c)].slice(0, o), p = new Set(f.map((e) => e.id));
94
+ return d.forEach((e) => {
95
+ p.has(e.id) || n(e.id);
96
+ }), e((e) => ({
97
+ items: f,
98
+ history: [u, ...e.history.filter((e) => e.id !== c)]
99
+ })), r(i, c, u.ttlMs), c;
92
100
  },
93
101
  update: (t, n) => {
94
- let { items: a } = s(), o = a.findIndex((e) => e.id === t);
95
- if (o === -1) return !1;
96
- let c = a[o], l = {
97
- ...c,
102
+ let a = s().items.find((e) => e.id === t);
103
+ if (!a) return !1;
104
+ let o = {
105
+ ...a,
98
106
  ...n,
99
- id: c.id,
100
- createdAt: c.createdAt
107
+ id: a.id,
108
+ createdAt: a.createdAt
101
109
  };
102
- return e((e) => ({ items: e.items.map((e) => e.id === t ? l : e) })), "ttlMs" in n && r(i, t, l.ttlMs), !0;
110
+ return e((e) => ({
111
+ items: e.items.map((e) => e.id === t ? o : e),
112
+ history: e.history.map((e) => e.id === t ? o : e)
113
+ })), "ttlMs" in n && r(i, t, o.ttlMs), !0;
103
114
  },
104
115
  upsert: (e) => s().actions.update(e.id, {
105
116
  type: e.type,
@@ -113,11 +124,14 @@ var t = null, n = (e) => (t = e, () => {
113
124
  },
114
125
  clear: () => {
115
126
  t.forEach((e) => window.clearTimeout(e)), t.clear(), e({ items: [] });
127
+ },
128
+ clearHistory: () => {
129
+ e({ history: [] });
116
130
  }
117
131
  }
118
132
  }));
119
133
  return i;
120
- };
134
+ }
121
135
  //#endregion
122
136
  export { n as bindNotifications, s as createNotificationsStore, i as notify };
123
137
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/notifications/notify.ts","../../src/notifications/store.ts"],"sourcesContent":["import type { NotificationPushInput, NotificationsStoreApi, NotificationUpdatePatch } from \"./store\";\nimport type { NotificationId } from \"./types\";\n\nlet boundStore: NotificationsStoreApi | null = null;\n\nexport const bindNotifications = (store: NotificationsStoreApi) => {\n\tboundStore = store;\n\n\treturn () => {\n\t\tif (boundStore === store) {\n\t\t\tboundStore = null;\n\t\t}\n\t};\n};\n\nconst requireStore = (): NotificationsStoreApi => {\n\tif (!boundStore) throw new Error(\"Notifications store is not bound. Wrap app with NotificationsProvider.\");\n\treturn boundStore;\n};\n\nexport const notify = Object.freeze({\n\tpush: (input: NotificationPushInput) => requireStore().getState().actions.push(input),\n\tupsert: (input: NotificationPushInput & { id: NotificationId }) => requireStore().getState().actions.upsert(input),\n\tupdate: (id: NotificationId, patch: NotificationUpdatePatch) => requireStore().getState().actions.update(id, patch),\n\n\tdismiss: (id: NotificationId) => requireStore().getState().actions.dismiss(id),\n\tclear: () => requireStore().getState().actions.clear(),\n\n\tsuccess: (message: string, opts?: Omit<NotificationPushInput, \"type\" | \"message\">) =>\n\t\trequireStore()\n\t\t\t.getState()\n\t\t\t.actions.push({ type: \"success\", message, ...opts }),\n\tinfo: (message: string, opts?: Omit<NotificationPushInput, \"type\" | \"message\">) =>\n\t\trequireStore()\n\t\t\t.getState()\n\t\t\t.actions.push({ type: \"info\", message, ...opts }),\n\twarning: (message: string, opts?: Omit<NotificationPushInput, \"type\" | \"message\">) =>\n\t\trequireStore()\n\t\t\t.getState()\n\t\t\t.actions.push({ type: \"warning\", message, ...opts }),\n\terror: (message: string, opts?: Omit<NotificationPushInput, \"type\" | \"message\">) =>\n\t\trequireStore()\n\t\t\t.getState()\n\t\t\t.actions.push({ type: \"error\", message, ...opts }),\n\n\t/**\n\t * Удобный паттерн \"долгой операции\":\n\t * const op = notify.progress(\"Сохраняю...\");\n\t * ... op.success(\"Готово\") или op.error(\"Ошибка\")\n\t */\n\tprogress: (message: string, opts?: { id?: NotificationId; title?: string }) => {\n\t\tconst store = requireStore();\n\t\tconst id = opts?.id;\n\n\t\tif (id) {\n\t\t\tstore.getState().actions.upsert({\n\t\t\t\tid,\n\t\t\t\ttype: \"info\",\n\t\t\t\ttitle: opts?.title,\n\t\t\t\tmessage,\n\t\t\t\tdismissible: false\n\t\t\t});\n\t\t}\n\n\t\tconst notificationId =\n\t\t\tid ??\n\t\t\tstore.getState().actions.push({\n\t\t\t\ttype: \"info\",\n\t\t\t\ttitle: opts?.title,\n\t\t\t\tmessage,\n\t\t\t\tdismissible: false\n\t\t\t});\n\n\t\treturn {\n\t\t\tid: notificationId,\n\t\t\tupdate: (nextMessage: string) => notify.update(notificationId, { message: nextMessage }),\n\t\t\tsuccess: (nextMessage: string, ttlMs = 2500) =>\n\t\t\t\tnotify.update(notificationId, { type: \"success\", message: nextMessage, dismissible: true, ttlMs }),\n\t\t\terror: (nextMessage: string, ttlMs = 5000) =>\n\t\t\t\tnotify.update(notificationId, { type: \"error\", message: nextMessage, dismissible: true, ttlMs }),\n\t\t\tdismiss: () => notify.dismiss(notificationId)\n\t\t};\n\t}\n});\n","import { createStore, StoreApi } from \"zustand\";\n\nimport { Notification, NotificationAction, NotificationId, NotificationType } from \"./types\";\n\nexport type NotificationPushInput = {\n\tid?: NotificationId;\n\ttype: NotificationType;\n\ttitle?: string;\n\tmessage: string;\n\tttlMs?: number;\n\tdismissible?: boolean;\n\tactions?: NotificationAction[];\n};\n\nexport type NotificationUpdatePatch = Partial<Omit<Notification, \"id\" | \"createdAt\">> & {\n\t// ttlMs можно менять; createdAt — нет\n};\n\ntype NotificationsState = {\n\titems: Notification[];\n};\n\ntype NotificationsActions = {\n\tpush: (input: NotificationPushInput) => NotificationId;\n\tupdate: (id: NotificationId, patch: NotificationUpdatePatch) => boolean;\n\tupsert: (input: NotificationPushInput & { id: NotificationId }) => NotificationId;\n\tdismiss: (id: NotificationId) => void;\n\tclear: () => void;\n};\n\nexport type NotificationsStore = NotificationsState & {\n\tactions: NotificationsActions;\n};\n\nconst genId = (): NotificationId => `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;\n\nconst MAX_ITEMS = 6;\n\nexport const createNotificationsStore = () => {\n\tconst timers = new Map<NotificationId, number>();\n\n\tconst clearTimer = (id: NotificationId) => {\n\t\tconst t = timers.get(id);\n\t\tif (t) {\n\t\t\twindow.clearTimeout(t);\n\t\t\ttimers.delete(id);\n\t\t}\n\t};\n\n\tconst armTimer = (store: StoreApi<NotificationsStore>, id: NotificationId, ttlMs: number = 10_000) => {\n\t\tclearTimer(id);\n\t\tif (ttlMs <= 0) return;\n\n\t\tconst t = window.setTimeout(() => {\n\t\t\tstore.getState().actions.dismiss(id);\n\t\t}, ttlMs);\n\n\t\ttimers.set(id, t);\n\t};\n\n\tconst store = createStore<NotificationsStore>()((set, get) => ({\n\t\titems: [],\n\n\t\tactions: {\n\t\t\tpush: (input) => {\n\t\t\t\tconst id = input.id ?? genId();\n\t\t\t\tconst now = Date.now();\n\n\t\t\t\tconst notif: Notification = {\n\t\t\t\t\tid,\n\t\t\t\t\ttype: input.type,\n\t\t\t\t\ttitle: input.title,\n\t\t\t\t\tmessage: input.message,\n\t\t\t\t\tcreatedAt: now,\n\t\t\t\t\tttlMs: input.ttlMs,\n\t\t\t\t\tdismissible: input.dismissible ?? true,\n\t\t\t\t\tactions: input.actions\n\t\t\t\t};\n\n\t\t\t\tset((s) => ({\n\t\t\t\t\titems: [notif, ...s.items.filter((n) => n.id !== id)].slice(0, MAX_ITEMS)\n\t\t\t\t}));\n\n\t\t\t\tarmTimer(store, id, notif.ttlMs);\n\t\t\t\treturn id;\n\t\t\t},\n\n\t\t\tupdate: (id, patch) => {\n\t\t\t\tconst { items } = get();\n\t\t\t\tconst idx = items.findIndex((n) => n.id === id);\n\t\t\t\tif (idx === -1) return false;\n\n\t\t\t\tconst prev = items[idx];\n\t\t\t\tconst next: Notification = {\n\t\t\t\t\t...prev,\n\t\t\t\t\t...patch,\n\t\t\t\t\tid: prev.id,\n\t\t\t\t\tcreatedAt: prev.createdAt\n\t\t\t\t};\n\n\t\t\t\tset((s) => ({\n\t\t\t\t\titems: s.items.map((n) => (n.id === id ? next : n))\n\t\t\t\t}));\n\n\t\t\t\t// если ttlMs изменили — пере-армим таймер\n\t\t\t\tif (\"ttlMs\" in patch) {\n\t\t\t\t\tarmTimer(store, id, next.ttlMs);\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t},\n\n\t\t\tupsert: (input) => {\n\t\t\t\tconst ok = get().actions.update(input.id, {\n\t\t\t\t\ttype: input.type,\n\t\t\t\t\ttitle: input.title,\n\t\t\t\t\tmessage: input.message,\n\t\t\t\t\tttlMs: input.ttlMs,\n\t\t\t\t\tdismissible: input.dismissible\n\t\t\t\t});\n\n\t\t\t\tif (ok) return input.id;\n\t\t\t\treturn get().actions.push(input);\n\t\t\t},\n\n\t\t\tdismiss: (id) => {\n\t\t\t\tclearTimer(id);\n\t\t\t\tset((s) => ({ items: s.items.filter((n) => n.id !== id) }));\n\t\t\t},\n\n\t\t\tclear: () => {\n\t\t\t\ttimers.forEach((t) => window.clearTimeout(t));\n\t\t\t\ttimers.clear();\n\t\t\t\tset({ items: [] });\n\t\t\t}\n\t\t}\n\t}));\n\n\treturn store;\n};\n\nexport type NotificationsStoreApi = StoreApi<NotificationsStore>;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/notifications/notify.ts","../../src/notifications/store.ts"],"sourcesContent":["import { type NotificationPushInput, type NotificationsStoreApi, type NotificationUpdatePatch } from \"./store\";\nimport { type NotificationId } from \"./types\";\n\nlet boundStore: NotificationsStoreApi | null = null;\n\nexport const bindNotifications = (store: NotificationsStoreApi) => {\n\tboundStore = store;\n\n\treturn () => {\n\t\tif (boundStore === store) {\n\t\t\tboundStore = null;\n\t\t}\n\t};\n};\n\nconst requireStore = (): NotificationsStoreApi => {\n\tif (!boundStore) throw new Error(\"Notifications store is not bound. Wrap app with NotificationsProvider.\");\n\treturn boundStore;\n};\n\nexport const notify = Object.freeze({\n\tpush: (input: NotificationPushInput) => requireStore().getState().actions.push(input),\n\tupsert: (input: NotificationPushInput & { id: NotificationId }) => requireStore().getState().actions.upsert(input),\n\tupdate: (id: NotificationId, patch: NotificationUpdatePatch) => requireStore().getState().actions.update(id, patch),\n\n\tdismiss: (id: NotificationId) => requireStore().getState().actions.dismiss(id),\n\tclear: () => requireStore().getState().actions.clear(),\n\tclearHistory: () => requireStore().getState().actions.clearHistory(),\n\n\tsuccess: (message: string, opts?: Omit<NotificationPushInput, \"type\" | \"message\">) =>\n\t\trequireStore()\n\t\t\t.getState()\n\t\t\t.actions.push({ type: \"success\", message, ...opts }),\n\tinfo: (message: string, opts?: Omit<NotificationPushInput, \"type\" | \"message\">) =>\n\t\trequireStore()\n\t\t\t.getState()\n\t\t\t.actions.push({ type: \"info\", message, ...opts }),\n\twarning: (message: string, opts?: Omit<NotificationPushInput, \"type\" | \"message\">) =>\n\t\trequireStore()\n\t\t\t.getState()\n\t\t\t.actions.push({ type: \"warning\", message, ...opts }),\n\terror: (message: string, opts?: Omit<NotificationPushInput, \"type\" | \"message\">) =>\n\t\trequireStore()\n\t\t\t.getState()\n\t\t\t.actions.push({ type: \"error\", message, ...opts }),\n\n\t/**\n\t * Удобный паттерн \"долгой операции\":\n\t * const op = notify.progress(\"Сохраняю...\");\n\t * ... op.success(\"Готово\") или op.error(\"Ошибка\")\n\t */\n\tprogress: (message: string, opts?: { id?: NotificationId; title?: string }) => {\n\t\tconst store = requireStore();\n\t\tconst id = opts?.id;\n\n\t\tif (id) {\n\t\t\tstore.getState().actions.upsert({\n\t\t\t\tid,\n\t\t\t\ttype: \"info\",\n\t\t\t\ttitle: opts?.title,\n\t\t\t\tmessage,\n\t\t\t\tdismissible: false\n\t\t\t});\n\t\t}\n\n\t\tconst notificationId =\n\t\t\tid ??\n\t\t\tstore.getState().actions.push({\n\t\t\t\ttype: \"info\",\n\t\t\t\ttitle: opts?.title,\n\t\t\t\tmessage,\n\t\t\t\tdismissible: false\n\t\t\t});\n\n\t\treturn {\n\t\t\tid: notificationId,\n\t\t\tupdate: (nextMessage: string) => notify.update(notificationId, { message: nextMessage }),\n\t\t\tsuccess: (nextMessage: string, ttlMs = 2500) =>\n\t\t\t\tnotify.update(notificationId, { type: \"success\", message: nextMessage, dismissible: true, ttlMs }),\n\t\t\terror: (nextMessage: string, ttlMs = 5000) =>\n\t\t\t\tnotify.update(notificationId, { type: \"error\", message: nextMessage, dismissible: true, ttlMs }),\n\t\t\tdismiss: () => notify.dismiss(notificationId)\n\t\t};\n\t}\n});\n","import { createStore, type StoreApi } from \"zustand\";\n\nimport { type Notification, type NotificationAction, type NotificationId, type NotificationType } from \"./types\";\n\nexport type NotificationPushInput = {\n\tid?: NotificationId;\n\ttype: NotificationType;\n\ttitle?: string;\n\tmessage: string;\n\tttlMs?: number;\n\tdismissible?: boolean;\n\tactions?: NotificationAction[];\n};\n\nexport type NotificationUpdatePatch = Partial<Omit<Notification, \"id\" | \"createdAt\">> & {\n\t// ttlMs можно менять; createdAt — нет\n};\n\nexport type NotificationsState = {\n\t/** Активные toast-уведомления, которые должен отображать краткоживущий host. */\n\titems: Notification[];\n\t/** Полная история за время жизни store; TTL, dismiss и переполнение toast-стека её не сокращают. */\n\thistory: Notification[];\n};\n\nexport type NotificationsActions = {\n\tpush: (input: NotificationPushInput) => NotificationId;\n\tupdate: (id: NotificationId, patch: NotificationUpdatePatch) => boolean;\n\tupsert: (input: NotificationPushInput & { id: NotificationId }) => NotificationId;\n\tdismiss: (id: NotificationId) => void;\n\tclear: () => void;\n\tclearHistory: () => void;\n};\n\nexport type NotificationsStore = NotificationsState & {\n\tactions: NotificationsActions;\n};\n\nconst genId = (): NotificationId => `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;\n\nconst MAX_VISIBLE_ITEMS = 6;\n\nexport function createNotificationsStore() {\n\tconst timers = new Map<NotificationId, number>();\n\n\tconst clearTimer = (id: NotificationId) => {\n\t\tconst t = timers.get(id);\n\t\tif (t) {\n\t\t\twindow.clearTimeout(t);\n\t\t\ttimers.delete(id);\n\t\t}\n\t};\n\n\tconst armTimer = (store: StoreApi<NotificationsStore>, id: NotificationId, ttlMs: number = 10_000) => {\n\t\tclearTimer(id);\n\t\tif (ttlMs <= 0) return;\n\n\t\tconst t = window.setTimeout(() => {\n\t\t\tstore.getState().actions.dismiss(id);\n\t\t}, ttlMs);\n\n\t\ttimers.set(id, t);\n\t};\n\n\tconst store = createStore<NotificationsStore>()((set, get) => ({\n\t\titems: [],\n\t\thistory: [],\n\n\t\tactions: {\n\t\t\tpush: (input) => {\n\t\t\t\tconst id = input.id ?? genId();\n\t\t\t\tconst now = Date.now();\n\n\t\t\t\tconst notif: Notification = {\n\t\t\t\t\tid,\n\t\t\t\t\ttype: input.type,\n\t\t\t\t\ttitle: input.title,\n\t\t\t\t\tmessage: input.message,\n\t\t\t\t\tcreatedAt: now,\n\t\t\t\t\tttlMs: input.ttlMs,\n\t\t\t\t\tdismissible: input.dismissible ?? true,\n\t\t\t\t\tactions: input.actions\n\t\t\t\t};\n\n\t\t\t\tconst currentItems = get().items;\n\t\t\t\tconst nextItems = [notif, ...currentItems.filter((notification) => notification.id !== id)].slice(0, MAX_VISIBLE_ITEMS);\n\t\t\t\tconst visibleIds = new Set(nextItems.map((notification) => notification.id));\n\n\t\t\t\t// Для вытесненного toast таймер больше не нужен: запись уже останется в history.\n\t\t\t\tcurrentItems.forEach((notification) => {\n\t\t\t\t\tif (!visibleIds.has(notification.id)) clearTimer(notification.id);\n\t\t\t\t});\n\n\t\t\t\tset((state) => ({\n\t\t\t\t\titems: nextItems,\n\t\t\t\t\thistory: [notif, ...state.history.filter((notification) => notification.id !== id)]\n\t\t\t\t}));\n\n\t\t\t\tarmTimer(store, id, notif.ttlMs);\n\t\t\t\treturn id;\n\t\t\t},\n\n\t\t\tupdate: (id, patch) => {\n\t\t\t\tconst previous = get().items.find((notification) => notification.id === id);\n\t\t\t\tif (!previous) return false;\n\n\t\t\t\tconst next: Notification = {\n\t\t\t\t\t...previous,\n\t\t\t\t\t...patch,\n\t\t\t\t\tid: previous.id,\n\t\t\t\t\tcreatedAt: previous.createdAt\n\t\t\t\t};\n\n\t\t\t\tset((state) => ({\n\t\t\t\t\titems: state.items.map((notification) => (notification.id === id ? next : notification)),\n\t\t\t\t\thistory: state.history.map((notification) => (notification.id === id ? next : notification))\n\t\t\t\t}));\n\n\t\t\t\t// если ttlMs изменили — пере-армим таймер\n\t\t\t\tif (\"ttlMs\" in patch) {\n\t\t\t\t\tarmTimer(store, id, next.ttlMs);\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t},\n\n\t\t\tupsert: (input) => {\n\t\t\t\tconst ok = get().actions.update(input.id, {\n\t\t\t\t\ttype: input.type,\n\t\t\t\t\ttitle: input.title,\n\t\t\t\t\tmessage: input.message,\n\t\t\t\t\tttlMs: input.ttlMs,\n\t\t\t\t\tdismissible: input.dismissible\n\t\t\t\t});\n\n\t\t\t\tif (ok) return input.id;\n\t\t\t\treturn get().actions.push(input);\n\t\t\t},\n\n\t\t\tdismiss: (id) => {\n\t\t\t\tclearTimer(id);\n\t\t\t\tset((s) => ({ items: s.items.filter((n) => n.id !== id) }));\n\t\t\t},\n\n\t\t\tclear: () => {\n\t\t\t\ttimers.forEach((t) => window.clearTimeout(t));\n\t\t\t\ttimers.clear();\n\t\t\t\tset({ items: [] });\n\t\t\t},\n\n\t\t\tclearHistory: () => {\n\t\t\t\tset({ history: [] });\n\t\t\t}\n\t\t}\n\t}));\n\n\treturn store;\n}\n\nexport type NotificationsStoreApi = StoreApi<NotificationsStore>;\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import type { NotificationPushInput, NotificationsStoreApi, NotificationUpdatePatch } from "./store";
2
- import type { NotificationId } from "./types";
1
+ import { type NotificationPushInput, type NotificationsStoreApi, type NotificationUpdatePatch } from "./store";
2
+ import { type NotificationId } from "./types";
3
3
  export declare const bindNotifications: (store: NotificationsStoreApi) => () => void;
4
4
  export declare const notify: Readonly<{
5
5
  push: (input: NotificationPushInput) => string;
@@ -9,6 +9,7 @@ export declare const notify: Readonly<{
9
9
  update: (id: NotificationId, patch: NotificationUpdatePatch) => boolean;
10
10
  dismiss: (id: NotificationId) => void;
11
11
  clear: () => void;
12
+ clearHistory: () => void;
12
13
  success: (message: string, opts?: Omit<NotificationPushInput, "type" | "message">) => string;
13
14
  info: (message: string, opts?: Omit<NotificationPushInput, "type" | "message">) => string;
14
15
  warning: (message: string, opts?: Omit<NotificationPushInput, "type" | "message">) => string;
@@ -1 +1 @@
1
- {"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/notifications/notify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AACrG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAI9C,eAAO,MAAM,iBAAiB,GAAI,OAAO,qBAAqB,eAQ7D,CAAC;AAOF,eAAO,MAAM,MAAM;kBACJ,qBAAqB;oBACnB,qBAAqB,GAAG;QAAE,EAAE,EAAE,cAAc,CAAA;KAAE;iBACjD,cAAc,SAAS,uBAAuB;kBAE7C,cAAc;;uBAGT,MAAM,SAAS,IAAI,CAAC,qBAAqB,EAAE,MAAM,GAAG,SAAS,CAAC;oBAIjE,MAAM,SAAS,IAAI,CAAC,qBAAqB,EAAE,MAAM,GAAG,SAAS,CAAC;uBAI3D,MAAM,SAAS,IAAI,CAAC,qBAAqB,EAAE,MAAM,GAAG,SAAS,CAAC;qBAIhE,MAAM,SAAS,IAAI,CAAC,qBAAqB,EAAE,MAAM,GAAG,SAAS,CAAC;IAK/E;;;;OAIG;wBACiB,MAAM,SAAS;QAAE,EAAE,CAAC,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;;8BAyBlD,MAAM;+BACL,MAAM;6BAER,MAAM;;;EAK5B,CAAC"}
1
+ {"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/notifications/notify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,qBAAqB,EAAE,KAAK,qBAAqB,EAAE,KAAK,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAC/G,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAI9C,eAAO,MAAM,iBAAiB,GAAI,OAAO,qBAAqB,eAQ7D,CAAC;AAOF,eAAO,MAAM,MAAM;kBACJ,qBAAqB;oBACnB,qBAAqB,GAAG;QAAE,EAAE,EAAE,cAAc,CAAA;KAAE;iBACjD,cAAc,SAAS,uBAAuB;kBAE7C,cAAc;;;uBAIT,MAAM,SAAS,IAAI,CAAC,qBAAqB,EAAE,MAAM,GAAG,SAAS,CAAC;oBAIjE,MAAM,SAAS,IAAI,CAAC,qBAAqB,EAAE,MAAM,GAAG,SAAS,CAAC;uBAI3D,MAAM,SAAS,IAAI,CAAC,qBAAqB,EAAE,MAAM,GAAG,SAAS,CAAC;qBAIhE,MAAM,SAAS,IAAI,CAAC,qBAAqB,EAAE,MAAM,GAAG,SAAS,CAAC;IAK/E;;;;OAIG;wBACiB,MAAM,SAAS;QAAE,EAAE,CAAC,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;;8BAyBlD,MAAM;+BACL,MAAM;6BAER,MAAM;;;EAK5B,CAAC"}
@@ -1,5 +1,5 @@
1
- import { StoreApi } from "zustand";
2
- import { Notification, NotificationAction, NotificationId, NotificationType } from "./types";
1
+ import { type StoreApi } from "zustand";
2
+ import { type Notification, type NotificationAction, type NotificationId, type NotificationType } from "./types";
3
3
  export type NotificationPushInput = {
4
4
  id?: NotificationId;
5
5
  type: NotificationType;
@@ -10,10 +10,13 @@ export type NotificationPushInput = {
10
10
  actions?: NotificationAction[];
11
11
  };
12
12
  export type NotificationUpdatePatch = Partial<Omit<Notification, "id" | "createdAt">> & {};
13
- type NotificationsState = {
13
+ export type NotificationsState = {
14
+ /** Активные toast-уведомления, которые должен отображать краткоживущий host. */
14
15
  items: Notification[];
16
+ /** Полная история за время жизни store; TTL, dismiss и переполнение toast-стека её не сокращают. */
17
+ history: Notification[];
15
18
  };
16
- type NotificationsActions = {
19
+ export type NotificationsActions = {
17
20
  push: (input: NotificationPushInput) => NotificationId;
18
21
  update: (id: NotificationId, patch: NotificationUpdatePatch) => boolean;
19
22
  upsert: (input: NotificationPushInput & {
@@ -21,11 +24,11 @@ type NotificationsActions = {
21
24
  }) => NotificationId;
22
25
  dismiss: (id: NotificationId) => void;
23
26
  clear: () => void;
27
+ clearHistory: () => void;
24
28
  };
25
29
  export type NotificationsStore = NotificationsState & {
26
30
  actions: NotificationsActions;
27
31
  };
28
- export declare const createNotificationsStore: () => StoreApi<NotificationsStore>;
32
+ export declare function createNotificationsStore(): StoreApi<NotificationsStore>;
29
33
  export type NotificationsStoreApi = StoreApi<NotificationsStore>;
30
- export {};
31
34
  //# sourceMappingURL=store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/notifications/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE7F,MAAM,MAAM,qBAAqB,GAAG;IACnC,EAAE,CAAC,EAAE,cAAc,CAAC;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,GAAG,WAAW,CAAC,CAAC,GAAG,EAEvF,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACzB,KAAK,EAAE,YAAY,EAAE,CAAC;CACtB,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC3B,IAAI,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,cAAc,CAAC;IACvD,MAAM,EAAE,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,uBAAuB,KAAK,OAAO,CAAC;IACxE,MAAM,EAAE,CAAC,KAAK,EAAE,qBAAqB,GAAG;QAAE,EAAE,EAAE,cAAc,CAAA;KAAE,KAAK,cAAc,CAAC;IAClF,OAAO,EAAE,CAAC,EAAE,EAAE,cAAc,KAAK,IAAI,CAAC;IACtC,KAAK,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,kBAAkB,GAAG;IACrD,OAAO,EAAE,oBAAoB,CAAC;CAC9B,CAAC;AAMF,eAAO,MAAM,wBAAwB,oCAqGpC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC,kBAAkB,CAAC,CAAC"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/notifications/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAErD,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,kBAAkB,EAAE,KAAK,cAAc,EAAE,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEjH,MAAM,MAAM,qBAAqB,GAAG;IACnC,EAAE,CAAC,EAAE,cAAc,CAAC;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,GAAG,WAAW,CAAC,CAAC,GAAG,EAEvF,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAChC,gFAAgF;IAChF,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,oGAAoG;IACpG,OAAO,EAAE,YAAY,EAAE,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IAClC,IAAI,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,cAAc,CAAC;IACvD,MAAM,EAAE,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,uBAAuB,KAAK,OAAO,CAAC;IACxE,MAAM,EAAE,CAAC,KAAK,EAAE,qBAAqB,GAAG;QAAE,EAAE,EAAE,cAAc,CAAA;KAAE,KAAK,cAAc,CAAC;IAClF,OAAO,EAAE,CAAC,EAAE,EAAE,cAAc,KAAK,IAAI,CAAC;IACtC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,YAAY,EAAE,MAAM,IAAI,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,kBAAkB,GAAG;IACrD,OAAO,EAAE,oBAAoB,CAAC;CAC9B,CAAC;AAMF,wBAAgB,wBAAwB,iCAmHvC;AAED,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC,kBAAkB,CAAC,CAAC"}
@@ -111,88 +111,103 @@ function m(e, n) {
111
111
  //#endregion
112
112
  //#region src/query-client/queryPersistenceAdapter.ts
113
113
  var h = {};
114
- function g(e) {
114
+ function ee(e) {
115
115
  h = e;
116
116
  }
117
- function _() {
117
+ function g() {
118
118
  return h;
119
119
  }
120
120
  //#endregion
121
121
  //#region src/query-client/persistence.ts
122
- var v = "queries", y = "session", b = __REACT_QUERY_PERSISTENCE_BUSTER__, x = 7776e6, S = null;
123
- function C(e) {
122
+ var _ = "queries", v = "session", y = __REACT_QUERY_PERSISTENCE_BUSTER__, b = 7776e6, x = null;
123
+ function S(e) {
124
124
  if (e) return e;
125
125
  if (globalThis.indexedDB !== void 0) return globalThis.indexedDB;
126
126
  }
127
- function w(e) {
127
+ function C(e) {
128
128
  return new Promise((t, n) => {
129
129
  e.onsuccess = () => t(e.result), e.onerror = () => n(e.error);
130
130
  });
131
131
  }
132
- function T() {
132
+ function w() {
133
133
  if (__DEV__) return `dev:${__APP_ID__}`;
134
- let e = _().resolveSystemIdentifier?.();
134
+ let e = g().resolveSystemIdentifier?.();
135
135
  if (e) return e;
136
136
  let t = typeof window > "u" ? "server" : window.location.hostname;
137
137
  return `${__APP_ID__}:${t}`;
138
138
  }
139
- function E() {
140
- return `${T()}`;
139
+ function T() {
140
+ return `${w()}`;
141
141
  }
142
- function D(e) {
142
+ function E(e) {
143
143
  return encodeURIComponent(e);
144
144
  }
145
- function O() {
146
- return `${E()}-${y}-`;
145
+ function D() {
146
+ return `${T()}-${v}-`;
147
147
  }
148
- function k(e) {
149
- return `${O()}${D(e)}-`;
148
+ function O(e) {
149
+ return `${D()}${E(e)}-`;
150
150
  }
151
- function A(e, t) {
152
- return `${k(e)}${D(t)}`;
151
+ function k(e, t) {
152
+ return `${O(e)}${E(t)}`;
153
153
  }
154
- function j(e) {
154
+ function A(e) {
155
155
  return !!e?.id.trim() && Number.isFinite(Date.parse(e?.expiresAt ?? "")) && Date.parse(e?.expiresAt ?? "") > Date.now();
156
156
  }
157
- function M() {
158
- return j(S) ? S : null;
157
+ function j() {
158
+ return A(x) ? x : null;
159
159
  }
160
- async function N(e) {
161
- let t = R();
160
+ async function M(e) {
161
+ let t = z();
162
162
  if (!t?.entries) return;
163
163
  let n = await t.entries();
164
164
  await Promise.all(n.filter(([t]) => e(t)).map(([e]) => t.removeItem(e)));
165
165
  }
166
- async function P(e) {
167
- let t = e === void 0 ? O() : k(e);
168
- await N((e) => e.startsWith(t));
166
+ async function N(e) {
167
+ let t = e === void 0 ? D() : O(e);
168
+ await M((e) => e.startsWith(t));
169
169
  }
170
- async function F(e) {
171
- let t = e && j(e) ? {
170
+ async function P(e) {
171
+ let t = e && A(e) ? {
172
172
  id: e.id.trim(),
173
173
  expiresAt: e.expiresAt
174
174
  } : null;
175
- if (S = t, !t) {
176
- await P();
175
+ if (x = t, !t) {
176
+ await N();
177
177
  return;
178
178
  }
179
- let n = k(t.id);
180
- await N((e) => e.startsWith(O()) && !e.startsWith(n));
179
+ let n = O(t.id);
180
+ await M((e) => e.startsWith(D()) && !e.startsWith(n));
181
181
  }
182
- function I(e) {
182
+ function F(e) {
183
183
  return e.meta?.persist === !0 && e.meta.sessionScoped !== !0;
184
184
  }
185
- function L(e) {
185
+ function I(e) {
186
186
  let t = e.meta?.sessionPersistence;
187
- return e.meta?.sessionScoped !== !0 || !t || typeof t != "object" || !("cacheName" in t) || !("maxEntries" in t) || typeof t.cacheName != "string" || !/^[a-z0-9][a-z0-9_-]{0,63}$/u.test(t.cacheName) || !Number.isSafeInteger(t.maxEntries) || t.maxEntries < 1 || t.maxEntries > 5e3 ? null : {
187
+ if (e.meta?.sessionScoped !== !0 || !t || typeof t != "object" || !("cacheName" in t) || !("maxEntries" in t) || typeof t.cacheName != "string" || !/^[a-z0-9][a-z0-9_-]{0,63}$/u.test(t.cacheName) || !Number.isSafeInteger(t.maxEntries) || t.maxEntries < 1 || t.maxEntries > 5e3) return null;
188
+ let n = "refetchOnRestore" in t ? t.refetchOnRestore : void 0;
189
+ if (n !== void 0 && typeof n != "boolean") return null;
190
+ let r = "monotonicRevisionField" in t ? t.monotonicRevisionField : void 0;
191
+ return r !== void 0 && (typeof r != "string" || !/^[A-Za-z][A-Za-z0-9_]{0,63}$/u.test(r)) ? null : {
188
192
  cacheName: t.cacheName,
189
- maxEntries: t.maxEntries
193
+ maxEntries: t.maxEntries,
194
+ refetchOnRestore: n,
195
+ monotonicRevisionField: r
190
196
  };
191
197
  }
192
- function R(e = {}) {
193
- let t = C(e.indexedDB);
198
+ function L(e, t) {
199
+ let n = e.state.data;
200
+ if (typeof n != "object" || !n || Array.isArray(n)) return null;
201
+ let r = Reflect.get(n, t);
202
+ return typeof r != "string" || !/^\d+$/u.test(r) ? null : r.replace(/^0+(?=\d)/u, "");
203
+ }
204
+ function R(e, t) {
205
+ return e.length === t.length ? e === t ? 0 : e < t ? -1 : 1 : e.length - t.length;
206
+ }
207
+ function z(e = {}) {
208
+ let t = S(e.indexedDB);
194
209
  if (!t) return;
195
- let n = t, r = e.dbName ?? T(), i = e.storeName ?? v, a = null;
210
+ let n = t, r = e.dbName ?? w(), i = e.storeName ?? _, a = null;
196
211
  function o() {
197
212
  return a || (a = new Promise((e, t) => {
198
213
  let a = n.open(r, 1);
@@ -203,7 +218,7 @@ function R(e = {}) {
203
218
  }), a);
204
219
  }
205
220
  async function s(e, t) {
206
- return w(t((await o()).transaction(i, e).objectStore(i)));
221
+ return C(t((await o()).transaction(i, e).objectStore(i)));
207
222
  }
208
223
  async function c(e, t) {
209
224
  let n = await o();
@@ -252,73 +267,93 @@ function R(e = {}) {
252
267
  }
253
268
  };
254
269
  }
255
- function z() {
256
- let e = R();
270
+ function B() {
271
+ let e = z();
257
272
  if (!e) return;
258
273
  let t = e, n = r({
259
274
  storage: t,
260
275
  serialize: (e) => e,
261
276
  deserialize: (e) => e,
262
- prefix: E(),
263
- buster: _().persistenceBuster ?? b,
264
- maxAge: x,
277
+ prefix: T(),
278
+ buster: g().persistenceBuster ?? y,
279
+ maxAge: b,
265
280
  refetchOnRestore: !0,
266
- filters: { predicate: I }
281
+ filters: { predicate: F }
267
282
  }), i = /* @__PURE__ */ new Map();
268
- function a(e, n, a) {
269
- let o = A(e.id, n), s = `${o}:${a}:${e.expiresAt}`, c = i.get(s);
270
- if (c) return c;
271
- let l = Promise.resolve(), u = r({
283
+ function a(e, n, a, o = !0, s) {
284
+ let c = k(e.id, n), l = `${c}:${a}:${o}:${s ?? ""}:${e.expiresAt}`, u = i.get(l);
285
+ if (u) return u;
286
+ let d = Promise.resolve(), f = r({
272
287
  storage: {
273
- getItem: (n) => M()?.id === e.id ? t.getItem(n) : void 0,
288
+ getItem: (n) => j()?.id === e.id ? t.getItem(n) : void 0,
274
289
  removeItem: (e) => t.removeItem(e),
275
290
  entries: () => t.entries(),
276
291
  setItem: async (n, r) => {
277
- M()?.id === e.id && (await t.setItem(n, r), l = l.then(async () => {
278
- let e = (await t.entries()).filter(([e]) => e.startsWith(`${o}-`));
292
+ j()?.id === e.id && (s === void 0 ? await t.setItem(n, r) : await t.updateItem(n, (e) => {
293
+ if (!e || e.buster !== r.buster) return {
294
+ action: "set",
295
+ value: r,
296
+ result: void 0
297
+ };
298
+ let t = L(e, s), n = L(r, s);
299
+ return t !== null && n !== null && R(t, n) > 0 ? {
300
+ action: "keep",
301
+ result: void 0
302
+ } : {
303
+ action: "set",
304
+ value: r,
305
+ result: void 0
306
+ };
307
+ }), d = d.then(async () => {
308
+ let e = (await t.entries()).filter(([e]) => e.startsWith(`${c}-`));
279
309
  e.sort((e, t) => (t[1].state.dataUpdatedAt ?? 0) - (e[1].state.dataUpdatedAt ?? 0)), await Promise.all(e.slice(a).map(([e]) => t.removeItem(e)));
280
- }), await l);
310
+ }), await d);
281
311
  }
282
312
  },
283
313
  serialize: (e) => e,
284
314
  deserialize: (e) => e,
285
- prefix: o,
286
- buster: _().persistenceBuster ?? b,
287
- maxAge: x,
288
- refetchOnRestore: !0,
289
- filters: { predicate: (e) => L(e)?.cacheName === n }
315
+ prefix: c,
316
+ buster: g().persistenceBuster ?? y,
317
+ maxAge: b,
318
+ refetchOnRestore: o,
319
+ filters: { predicate: (e) => I(e)?.cacheName === n }
290
320
  });
291
- return i.set(s, u), u;
321
+ return i.set(l, f), f;
292
322
  }
293
323
  return {
294
324
  ...n,
295
325
  async persisterFn(e, t, r) {
296
- let i = L(r);
326
+ let i = I(r);
297
327
  if (!i) return n.persisterFn(e, t, r);
298
- let o = M();
299
- return o ? a(o, i.cacheName, i.maxEntries).persisterFn(e, t, r) : Promise.resolve(e(t));
328
+ let o = j();
329
+ return o ? a(o, i.cacheName, i.maxEntries, i.refetchOnRestore, i.monotonicRevisionField).persisterFn(e, t, r) : Promise.resolve(e(t));
300
330
  }
301
331
  };
302
332
  }
303
333
  //#endregion
304
334
  //#region src/query-client/queryMeta.ts
305
- var B = { persist: !0 }, V = { sessionScoped: !0 };
306
- function H(e) {
335
+ var V = { persist: !0 }, H = { sessionScoped: !0 };
336
+ function U(e) {
307
337
  let t = e.cacheName.trim();
308
338
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/u.test(t)) throw Error("Имя session query cache должно содержать только строчные латинские буквы, цифры, _ и -");
309
339
  if (!Number.isSafeInteger(e.maxEntries) || e.maxEntries < 1 || e.maxEntries > 5e3) throw Error("Лимит session query cache должен быть целым числом от 1 до 5000");
340
+ if (e.refetchOnRestore !== void 0 && typeof e.refetchOnRestore != "boolean") throw Error("Политика refetch после восстановления должна быть boolean");
341
+ let n = e.monotonicRevisionField?.trim();
342
+ if (n !== void 0 && !/^[A-Za-z][A-Za-z0-9_]{0,63}$/u.test(n)) throw Error("Имя поля монотонной ревизии должно быть JavaScript-идентификатором длиной до 64 символов");
310
343
  return {
311
344
  sessionScoped: !0,
312
345
  sessionPersistence: {
313
346
  cacheName: t,
314
- maxEntries: e.maxEntries
347
+ maxEntries: e.maxEntries,
348
+ ...e.refetchOnRestore === void 0 ? {} : { refetchOnRestore: e.refetchOnRestore },
349
+ ...n === void 0 ? {} : { monotonicRevisionField: n }
315
350
  }
316
351
  };
317
352
  }
318
353
  //#endregion
319
354
  //#region src/query-client/queryClient.ts
320
- function U({ onQueryError: e, onMutationError: t }) {
321
- let n = z();
355
+ function W({ onQueryError: e, onMutationError: t }) {
356
+ let n = B();
322
357
  return new o({
323
358
  queryCache: new a({ onError: e }),
324
359
  mutationCache: new i({ onError: t }),
@@ -337,35 +372,35 @@ function U({ onQueryError: e, onMutationError: t }) {
337
372
  }
338
373
  });
339
374
  }
340
- async function W(e, t, n) {
375
+ async function G(e, t, n) {
341
376
  await e.cancelQueries({ queryKey: t });
342
377
  let r = e.getQueryData(t);
343
378
  return e.setQueryData(t, n), { previous: r };
344
379
  }
345
- function G(e, t, n) {
380
+ function K(e, t, n) {
346
381
  n?.previous !== void 0 && e.setQueryData(t, n.previous);
347
382
  }
348
- function K(e, t, n, r) {
383
+ function q(e, t, n, r) {
349
384
  e.setQueryData(t, (e) => r(e, n));
350
385
  }
351
- async function q(e, t, n) {
352
- let r = n?.refetchType ?? "none", i = J(t) ? t : [t];
386
+ async function J(e, t, n) {
387
+ let r = n?.refetchType ?? "none", i = Y(t) ? t : [t];
353
388
  await Promise.all(i.map((t) => e.invalidateQueries({
354
389
  queryKey: t,
355
390
  refetchType: r
356
391
  })));
357
392
  }
358
- function J(e) {
393
+ function Y(e) {
359
394
  return Array.isArray(e) ? Array.isArray(e[0]) : !1;
360
395
  }
361
396
  //#endregion
362
397
  //#region src/query-client/sessionScoped.ts
363
- var Y = "session-scoped-query-cache-reset", X;
364
- function Z(e) {
398
+ var X = "session-scoped-query-cache-reset", Z;
399
+ function Q(e) {
365
400
  return e.meta?.sessionScoped === !0;
366
401
  }
367
- async function Q(e, t = {}) {
368
- let n = { predicate: Z };
402
+ async function $(e, t = {}) {
403
+ let n = { predicate: Q };
369
404
  await e.cancelQueries(n);
370
405
  let r = e.getQueryCache().findAll(n);
371
406
  for (let t of r) {
@@ -379,26 +414,26 @@ async function Q(e, t = {}) {
379
414
  });
380
415
  }
381
416
  await e.refetchQueries({
382
- predicate: (e) => Z(e) && e.getObserversCount() > 0,
417
+ predicate: (e) => Q(e) && e.getObserversCount() > 0,
383
418
  type: "active"
384
- }, { cancelRefetch: !0 }), t.broadcast !== !1 && X?.();
419
+ }, { cancelRefetch: !0 }), t.broadcast !== !1 && Z?.();
385
420
  }
386
- function $(e, t) {
421
+ function te(e, t) {
387
422
  if (typeof window > "u" || typeof BroadcastChannel > "u") return { cleanup: () => void 0 };
388
423
  let n = new BroadcastChannel(t.channelName), r = () => {
389
- let e = { type: Y };
424
+ let e = { type: X };
390
425
  n.postMessage(e);
391
426
  };
392
- return X = r, n.onmessage = (t) => {
393
- ee(t.data) && Q(e, { broadcast: !1 });
427
+ return Z = r, n.onmessage = (t) => {
428
+ ne(t.data) && $(e, { broadcast: !1 });
394
429
  }, { cleanup: () => {
395
- n.onmessage = null, n.close(), X === r && (X = void 0);
430
+ n.onmessage = null, n.close(), Z === r && (Z = void 0);
396
431
  } };
397
432
  }
398
- function ee(e) {
399
- return typeof e == "object" && !!e && "type" in e && e.type === Y;
433
+ function ne(e) {
434
+ return typeof e == "object" && !!e && "type" in e && e.type === X;
400
435
  }
401
436
  //#endregion
402
- export { b as REACT_QUERY_PERSISTENCE_BUSTER, x as REACT_QUERY_PERSISTENCE_MAX_AGE, l as broadcastCacheEvent, P as clearSessionQueryPersistence, g as configureQueryPersistenceProjectAdapter, R as createIndexedDbQueryStorage, U as createQueryClient, z as createReactQueryPersister, H as createSessionQueryPersistenceMeta, _ as getQueryPersistenceProjectAdapter, M as getSessionQueryPersistenceScope, m as installQueryInvalidationBroadcast, u as installReactQueryBroadcast, $ as installSessionScopedQueryReset, Z as isSessionScopedQuery, G as onErrorOptimistic, W as onMutateOptimistic, q as onSettledOptimistic, K as onSuccessOptimistic, B as persistedQueryMeta, Q as resetSessionScopedQueries, V as sessionScopedQueryMeta, c as setBroadcastFn, F as setSessionQueryPersistenceScope, I as shouldPersistQuery };
437
+ export { y as REACT_QUERY_PERSISTENCE_BUSTER, b as REACT_QUERY_PERSISTENCE_MAX_AGE, l as broadcastCacheEvent, N as clearSessionQueryPersistence, ee as configureQueryPersistenceProjectAdapter, z as createIndexedDbQueryStorage, W as createQueryClient, B as createReactQueryPersister, U as createSessionQueryPersistenceMeta, g as getQueryPersistenceProjectAdapter, j as getSessionQueryPersistenceScope, m as installQueryInvalidationBroadcast, u as installReactQueryBroadcast, te as installSessionScopedQueryReset, Q as isSessionScopedQuery, K as onErrorOptimistic, G as onMutateOptimistic, J as onSettledOptimistic, q as onSuccessOptimistic, V as persistedQueryMeta, $ as resetSessionScopedQueries, H as sessionScopedQueryMeta, c as setBroadcastFn, P as setSessionQueryPersistenceScope, F as shouldPersistQuery };
403
438
 
404
439
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/query-client/broadcast.ts","../../src/query-client/invalidationBroadcast.ts","../../src/query-client/queryPersistenceAdapter.ts","../../src/query-client/persistence.ts","../../src/query-client/queryMeta.ts","../../src/query-client/queryClient.ts","../../src/query-client/sessionScoped.ts"],"sourcesContent":["import { broadcastQueryClient } from \"@tanstack/query-broadcast-client-experimental\";\n\nimport { uuidv4 } from \"../crypto\";\nimport { isRecord } from \"../validators\";\n\nimport type { QueryClient, QueryKey } from \"@tanstack/react-query\";\n\ntype CacheSyncEvent =\n\t| { type: \"invalidate\"; keys: QueryKey[]; refetchType?: \"none\" | \"all\" | \"active\" | \"inactive\" }\n\t| { type: \"setQueryData\"; key: QueryKey; data: unknown };\n\ntype InstallBroadcastOptions = {\n\tqueriesChannel: string;\n\teventsChannel: string;\n};\n\ntype InstalledReactQueryBroadcast = {\n\tbroadcast: (event: CacheSyncEvent) => void;\n\tcleanup: () => void;\n};\n\ntype WireMessage = {\n\ttabId: string;\n\tevent: CacheSyncEvent;\n};\n\nlet broadcastFn: ((event: CacheSyncEvent) => void) | null = null;\n\nexport function setBroadcastFn(fn: (event: CacheSyncEvent) => void) {\n\tbroadcastFn = fn;\n}\n\nexport function broadcastCacheEvent(event: CacheSyncEvent) {\n\tbroadcastFn?.(event);\n}\n\nexport function installReactQueryBroadcast(queryClient: QueryClient, opts: InstallBroadcastOptions): InstalledReactQueryBroadcast {\n\tif (typeof window === \"undefined\") {\n\t\treturn { broadcast: () => {}, cleanup: () => {} };\n\t}\n\n\tlet isApplyingRemoteEvent = false;\n\n\t// 1) Sync query cache (data/state)\n\tconst cleanupQueryBroadcast = broadcastQueryClient({\n\t\tqueryClient,\n\t\tbroadcastChannel: opts.queriesChannel\n\t});\n\n\t// 2) Sync \"events\" (invalidate/setQueryData)\n\tconst tabId = uuidv4();\n\tconst bc = new BroadcastChannel(opts.eventsChannel);\n\tconst unsubscribeInvalidationBroadcast = queryClient.getQueryCache().subscribe((queryEvent) => {\n\t\tif (isApplyingRemoteEvent) return;\n\t\tif (queryEvent.type !== \"updated\" || queryEvent.action.type !== \"invalidate\") return;\n\n\t\tbroadcast({\n\t\t\ttype: \"invalidate\",\n\t\t\tkeys: [queryEvent.query.queryKey],\n\t\t\trefetchType: \"none\"\n\t\t});\n\t});\n\n\tbc.onmessage = (ev: MessageEvent) => {\n\t\t// MessageEvent.data: unknown -> делаем безопасное сужение без any\n\t\tconst data: unknown = ev.data;\n\t\tif (!isWireMessage(data)) return;\n\t\tif (data.tabId === tabId) return;\n\n\t\tconst e = data.event;\n\t\tisApplyingRemoteEvent = true;\n\n\t\ttry {\n\t\t\tif (e.type === \"invalidate\") {\n\t\t\t\tconst refetchType = e.refetchType ?? \"none\";\n\t\t\t\tfor (const key of e.keys) {\n\t\t\t\t\tqueryClient.invalidateQueries({ queryKey: key, refetchType });\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (e.type === \"setQueryData\") {\n\t\t\t\tqueryClient.setQueryData(e.key, e.data);\n\t\t\t\treturn;\n\t\t\t}\n\t\t} finally {\n\t\t\tisApplyingRemoteEvent = false;\n\t\t}\n\t};\n\n\tfunction broadcast(event: CacheSyncEvent) {\n\t\tconst msg: WireMessage = { tabId, event };\n\t\tbc.postMessage(msg);\n\t}\n\n\treturn {\n\t\tbroadcast,\n\t\tcleanup: () => {\n\t\t\tunsubscribeInvalidationBroadcast();\n\t\t\tcleanupQueryBroadcast();\n\t\t\tbc.close();\n\t\t}\n\t};\n}\n\nfunction isWireMessage(v: unknown): v is WireMessage {\n\tif (!isRecord(v)) return false;\n\tif (typeof v.tabId !== \"string\") return false;\n\tif (!(\"event\" in v)) return false;\n\treturn isCacheSyncEvent(v.event);\n}\n\nfunction isCacheSyncEvent(v: unknown): v is CacheSyncEvent {\n\tif (!isRecord(v)) return false;\n\tif (v.type === \"invalidate\") {\n\t\t// keys: QueryKey[] — runtime мы не сможем валидировать “идеально”,\n\t\t// но проверим что это массив, а refetchType — допустимое значение\n\t\tif (!Array.isArray(v.keys)) return false;\n\t\tif (v.refetchType === undefined) return true;\n\t\treturn v.refetchType === \"none\" || v.refetchType === \"active\" || v.refetchType === \"inactive\" || v.refetchType === \"all\";\n\t}\n\tif (v.type === \"setQueryData\") {\n\t\t// key должен быть массивом или строкой — QueryKey в рантайме обычно массив\n\t\t// проверим хотя бы наличие поля\n\t\treturn \"key\" in v && \"data\" in v;\n\t}\n\treturn false;\n}\n","import { uuidv4 } from \"../crypto\";\nimport { isRecord } from \"../validators\";\n\nimport type { QueryClient, QueryKey } from \"@tanstack/react-query\";\n\nexport type QueryInvalidationBroadcastOptions = {\n\t/** Уникальное в пределах origin имя канала приложения. */\n\treadonly channelName: string;\n\t/** Ограничивает как исходящие, так и входящие Query keys. */\n\treadonly matchesQuery: (queryKey: QueryKey) => boolean;\n\t/** Определяет, какие queries получающей вкладки будут перезапрошены сразу. */\n\treadonly refetchType?: \"none\" | \"active\" | \"inactive\" | \"all\";\n};\n\nexport type InstalledQueryInvalidationBroadcast = {\n\t/** Явно передаёт точечную инвалидацию без изменения локального QueryClient. */\n\treadonly broadcast: (queryKey: QueryKey) => void;\n\treadonly cleanup: () => void;\n};\n\ntype QueryInvalidationWireMessage = {\n\treadonly type: \"query.invalidate\";\n\treadonly senderId: string;\n\treadonly queryKey: QueryKey;\n};\n\nfunction isQueryInvalidationWireMessage(value: unknown): value is QueryInvalidationWireMessage {\n\treturn isRecord(value) && value.type === \"query.invalidate\" && typeof value.senderId === \"string\" && Array.isArray(value.queryKey);\n}\n\n/**\n * Передаёт между вкладками только факт инвалидации выбранного Query key.\n * Server data остаётся у QueryClient каждой вкладки и заново читается её queryFn.\n */\nexport function installQueryInvalidationBroadcast(\n\tqueryClient: QueryClient,\n\toptions: QueryInvalidationBroadcastOptions\n): InstalledQueryInvalidationBroadcast {\n\tif (typeof window === \"undefined\" || typeof BroadcastChannel === \"undefined\") {\n\t\treturn { broadcast: () => undefined, cleanup: () => undefined };\n\t}\n\n\tconst senderId = uuidv4();\n\tconst channel = new BroadcastChannel(options.channelName);\n\tlet applyingRemoteInvalidation = false;\n\tlet closed = false;\n\n\tconst broadcast = (queryKey: QueryKey) => {\n\t\tif (closed || !options.matchesQuery(queryKey)) return;\n\t\tchannel.postMessage({ type: \"query.invalidate\", senderId, queryKey } satisfies QueryInvalidationWireMessage);\n\t};\n\n\tconst unsubscribe = queryClient.getQueryCache().subscribe((event) => {\n\t\tif (applyingRemoteInvalidation || event.type !== \"updated\" || event.action.type !== \"invalidate\") return;\n\t\tbroadcast(event.query.queryKey);\n\t});\n\n\tchannel.onmessage = (event: MessageEvent<unknown>) => {\n\t\tconst message = event.data;\n\t\tif (closed || !isQueryInvalidationWireMessage(message) || message.senderId === senderId || !options.matchesQuery(message.queryKey))\n\t\t\treturn;\n\n\t\tapplyingRemoteInvalidation = true;\n\t\ttry {\n\t\t\tvoid queryClient.invalidateQueries({\n\t\t\t\tqueryKey: message.queryKey,\n\t\t\t\texact: true,\n\t\t\t\trefetchType: options.refetchType ?? \"active\"\n\t\t\t});\n\t\t} finally {\n\t\t\tapplyingRemoteInvalidation = false;\n\t\t}\n\t};\n\n\treturn {\n\t\tbroadcast,\n\t\tcleanup() {\n\t\t\tif (closed) return;\n\t\t\tclosed = true;\n\t\t\tunsubscribe();\n\t\t\tchannel.close();\n\t\t}\n\t};\n}\n","export type QueryPersistenceProjectAdapter = {\n\tpersistenceBuster?: string;\n\tresolveSystemIdentifier?: () => string | null | undefined;\n};\n\nlet queryPersistenceProjectAdapter: QueryPersistenceProjectAdapter = {};\n\nexport function configureQueryPersistenceProjectAdapter(adapter: QueryPersistenceProjectAdapter) {\n\tqueryPersistenceProjectAdapter = adapter;\n}\n\nexport function getQueryPersistenceProjectAdapter() {\n\treturn queryPersistenceProjectAdapter;\n}\n","import { experimental_createQueryPersister, type AsyncStorage, type PersistedQuery } from \"@tanstack/query-persist-client-core\";\nimport { type Query, type QueryFunction, type QueryFunctionContext, type QueryKey } from \"@tanstack/react-query\";\n\nimport { getQueryPersistenceProjectAdapter } from \"./queryPersistenceAdapter\";\n\nconst STORE_NAME = \"queries\";\nconst SESSION_PERSISTENCE_MARKER = \"session\";\n\nexport const REACT_QUERY_PERSISTENCE_BUSTER = __REACT_QUERY_PERSISTENCE_BUSTER__;\nexport const REACT_QUERY_PERSISTENCE_MAX_AGE = 90 * 24 * 60 * 60 * 1000;\n\ntype IndexedDbQueryStorageOptions = {\n\tdbName?: string;\n\tstoreName?: string;\n\tindexedDB?: IDBFactory;\n};\n\nexport type SessionQueryPersistenceScope = {\n\t/** Публичная identity серверной сессии; cookie secret или access token здесь недопустимы. */\n\treadonly id: string;\n\t/** Абсолютная граница, после которой snapshots этой сессии нельзя читать или записывать. */\n\treadonly expiresAt: string;\n};\n\nlet activeSessionPersistenceScope: SessionQueryPersistenceScope | null = null;\n\n/**\n * Описывает итог синхронного преобразования записи внутри одной IndexedDB-транзакции.\n * Отдельный result позволяет вернуть вызывающему коду вычисленный снимок только после commit.\n */\nexport type IndexedDbAtomicUpdateDecision<TStorageValue, TResult> =\n\t| { readonly action: \"keep\"; readonly result: TResult }\n\t| { readonly action: \"set\"; readonly value: TStorageValue; readonly result: TResult }\n\t| { readonly action: \"remove\"; readonly result: TResult };\n\nexport type IndexedDbQueryStorage<TStorageValue> = AsyncStorage<TStorageValue> & {\n\t/** Перечисляет записи object store для garbage collection и bounded persistence. */\n\treadonly entries: () => Promise<Array<[key: string, value: TStorageValue]>>;\n\t/**\n\t * Читает и изменяет одну запись в общей readwrite-транзакции, поэтому параллельные\n\t * вкладки не могут потерять уже зафиксированное изменение между get и set.\n\t * Callback должен быть синхронным: IndexedDB закрывает неактивную транзакцию до\n\t * завершения произвольной асинхронной работы.\n\t */\n\treadonly updateItem: <TResult>(\n\t\tkey: string,\n\t\tupdate: (value: TStorageValue | undefined) => IndexedDbAtomicUpdateDecision<TStorageValue, TResult>\n\t) => Promise<TResult>;\n};\n\nfunction resolveIndexedDbFactory(factory?: IDBFactory) {\n\tif (factory) return factory;\n\tif (typeof globalThis.indexedDB === \"undefined\") return undefined;\n\treturn globalThis.indexedDB;\n}\n\nfunction requestToPromise<T>(request: IDBRequest<T>) {\n\treturn new Promise<T>((resolve, reject) => {\n\t\trequest.onsuccess = () => resolve(request.result);\n\t\trequest.onerror = () => reject(request.error);\n\t});\n}\n\nfunction getSystemIdentifier() {\n\tif (__DEV__) {\n\t\treturn `dev:${__APP_ID__}`;\n\t}\n\n\tconst projectIdentifier = getQueryPersistenceProjectAdapter().resolveSystemIdentifier?.();\n\tif (projectIdentifier) return projectIdentifier;\n\n\tconst hostname = typeof window === \"undefined\" ? \"server\" : window.location.hostname;\n\treturn `${__APP_ID__}:${hostname}`;\n}\n\nfunction getPersistencePrefix() {\n\tconst system = getSystemIdentifier();\n\treturn `${system}`;\n}\n\nfunction encodePersistenceSegment(value: string) {\n\treturn encodeURIComponent(value);\n}\n\nfunction getSessionPersistenceRootPrefix() {\n\treturn `${getPersistencePrefix()}-${SESSION_PERSISTENCE_MARKER}-`;\n}\n\nfunction getSessionPersistenceScopePrefix(scopeId: string) {\n\treturn `${getSessionPersistenceRootPrefix()}${encodePersistenceSegment(scopeId)}-`;\n}\n\nfunction getSessionPersistenceCachePrefix(scopeId: string, cacheName: string) {\n\treturn `${getSessionPersistenceScopePrefix(scopeId)}${encodePersistenceSegment(cacheName)}`;\n}\n\nfunction isSessionPersistenceScopeValid(scope: SessionQueryPersistenceScope | null): scope is SessionQueryPersistenceScope {\n\treturn (\n\t\tBoolean(scope?.id.trim()) && Number.isFinite(Date.parse(scope?.expiresAt ?? \"\")) && Date.parse(scope?.expiresAt ?? \"\") > Date.now()\n\t);\n}\n\n/** Возвращает текущую browser-session identity, которой разрешено читать приватные persisted query. */\nexport function getSessionQueryPersistenceScope(): SessionQueryPersistenceScope | null {\n\treturn isSessionPersistenceScopeValid(activeSessionPersistenceScope) ? activeSessionPersistenceScope : null;\n}\n\nasync function removeSessionPersistenceEntries(predicate: (key: string) => boolean) {\n\tconst storage = createIndexedDbQueryStorage<PersistedQuery>();\n\tif (!storage?.entries) return;\n\n\tconst entries = await storage.entries();\n\tawait Promise.all(entries.filter(([key]) => predicate(key)).map(([key]) => storage.removeItem(key)));\n}\n\n/** Удаляет persisted query одной сессии или всех сессий текущего приложения. */\nexport async function clearSessionQueryPersistence(scopeId?: string): Promise<void> {\n\tconst prefix = scopeId === undefined ? getSessionPersistenceRootPrefix() : getSessionPersistenceScopePrefix(scopeId);\n\tawait removeSessionPersistenceEntries((key) => key.startsWith(prefix));\n}\n\n/**\n * Устанавливает единственную активную server-session identity и сразу исключает\n * чтение старого scope. Browser storage очищается асинхронно уже после смены\n * in-memory границы, поэтому параллельный query не может записаться старому пользователю.\n */\nexport async function setSessionQueryPersistenceScope(scope: SessionQueryPersistenceScope | null): Promise<void> {\n\tconst normalized = scope && isSessionPersistenceScopeValid(scope) ? { id: scope.id.trim(), expiresAt: scope.expiresAt } : null;\n\tactiveSessionPersistenceScope = normalized;\n\n\tif (!normalized) {\n\t\tawait clearSessionQueryPersistence();\n\t\treturn;\n\t}\n\n\tconst activePrefix = getSessionPersistenceScopePrefix(normalized.id);\n\tawait removeSessionPersistenceEntries((key) => key.startsWith(getSessionPersistenceRootPrefix()) && !key.startsWith(activePrefix));\n}\n\nexport function shouldPersistQuery(query: Pick<Query, \"meta\">) {\n\treturn query.meta?.persist === true && query.meta.sessionScoped !== true;\n}\n\nfunction readSessionQueryPersistencePolicy(query: Pick<Query, \"meta\">) {\n\tconst policy = query.meta?.sessionPersistence;\n\tif (query.meta?.sessionScoped !== true || !policy || typeof policy !== \"object\") return null;\n\tif (!(\"cacheName\" in policy) || !(\"maxEntries\" in policy)) return null;\n\tif (typeof policy.cacheName !== \"string\" || !/^[a-z0-9][a-z0-9_-]{0,63}$/u.test(policy.cacheName)) return null;\n\tif (!Number.isSafeInteger(policy.maxEntries) || policy.maxEntries < 1 || policy.maxEntries > 5_000) return null;\n\treturn { cacheName: policy.cacheName, maxEntries: policy.maxEntries } as const;\n}\n\nexport function createIndexedDbQueryStorage<TStorageValue = unknown>(\n\toptions: IndexedDbQueryStorageOptions = {}\n): IndexedDbQueryStorage<TStorageValue> | undefined {\n\tconst factory = resolveIndexedDbFactory(options.indexedDB);\n\tif (!factory) return undefined;\n\n\tconst idb = factory;\n\tconst dbName = options.dbName ?? getSystemIdentifier();\n\tconst storeName = options.storeName ?? STORE_NAME;\n\tlet dbPromise: Promise<IDBDatabase> | null = null;\n\n\tfunction openDb() {\n\t\tif (dbPromise) return dbPromise;\n\n\t\tdbPromise = new Promise<IDBDatabase>((resolve, reject) => {\n\t\t\tconst request = idb.open(dbName, 1);\n\n\t\t\trequest.onupgradeneeded = () => {\n\t\t\t\tconst db = request.result;\n\t\t\t\tif (!db.objectStoreNames.contains(storeName)) {\n\t\t\t\t\tdb.createObjectStore(storeName);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\trequest.onsuccess = () => resolve(request.result);\n\t\t\trequest.onerror = () => reject(request.error);\n\t\t\trequest.onblocked = () => reject(request.error ?? new Error(\"Открытие IndexedDB заблокировано\"));\n\t\t});\n\n\t\treturn dbPromise;\n\t}\n\n\tasync function runTransaction<T>(mode: IDBTransactionMode, action: (store: IDBObjectStore) => IDBRequest<T>) {\n\t\tconst db = await openDb();\n\t\tconst tx = db.transaction(storeName, mode);\n\t\tconst store = tx.objectStore(storeName);\n\t\tconst request = action(store);\n\t\treturn requestToPromise(request);\n\t}\n\n\tasync function runWrite(mode: IDBTransactionMode, action: (store: IDBObjectStore) => void) {\n\t\tconst db = await openDb();\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst tx = db.transaction(storeName, mode);\n\t\t\tconst store = tx.objectStore(storeName);\n\n\t\t\ttx.oncomplete = () => resolve();\n\t\t\ttx.onerror = () => reject(tx.error);\n\t\t\ttx.onabort = () => reject(tx.error);\n\n\t\t\ttry {\n\t\t\t\taction(store);\n\t\t\t} catch (error) {\n\t\t\t\ttx.abort();\n\t\t\t\treject(error);\n\t\t\t}\n\t\t});\n\t}\n\n\tasync function updateItem<TResult>(\n\t\tkey: string,\n\t\tupdate: (value: TStorageValue | undefined) => IndexedDbAtomicUpdateDecision<TStorageValue, TResult>\n\t) {\n\t\tconst db = await openDb();\n\t\treturn new Promise<TResult>((resolve, reject) => {\n\t\t\tconst tx = db.transaction(storeName, \"readwrite\");\n\t\t\tconst store = tx.objectStore(storeName);\n\t\t\tconst request = store.get(key);\n\t\t\tlet result: TResult;\n\t\t\tlet hasResult = false;\n\n\t\t\ttx.oncomplete = () => {\n\t\t\t\tif (hasResult) resolve(result);\n\t\t\t\telse reject(new Error(\"IndexedDB-транзакция завершилась без результата обновления\"));\n\t\t\t};\n\t\t\ttx.onerror = () => reject(tx.error);\n\t\t\ttx.onabort = () => reject(tx.error);\n\t\t\trequest.onerror = () => {\n\t\t\t\ttx.abort();\n\t\t\t\treject(request.error);\n\t\t\t};\n\t\t\trequest.onsuccess = () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst decision = update(request.result as TStorageValue | undefined);\n\t\t\t\t\tresult = decision.result;\n\t\t\t\t\thasResult = true;\n\t\t\t\t\tif (decision.action === \"set\") store.put(decision.value, key);\n\t\t\t\t\telse if (decision.action === \"remove\") store.delete(key);\n\t\t\t\t} catch (error) {\n\t\t\t\t\ttx.abort();\n\t\t\t\t\treject(error);\n\t\t\t\t}\n\t\t\t};\n\t\t});\n\t}\n\n\treturn {\n\t\tgetItem: (key) => runTransaction<TStorageValue | undefined>(\"readonly\", (store) => store.get(key)),\n\t\tsetItem: (key, value) => runWrite(\"readwrite\", (store) => store.put(value, key)),\n\t\tremoveItem: (key) => runWrite(\"readwrite\", (store) => store.delete(key)),\n\t\tupdateItem,\n\t\tentries: async () => {\n\t\t\tconst db = await openDb();\n\n\t\t\treturn new Promise<Array<[key: string, value: TStorageValue]>>((resolve, reject) => {\n\t\t\t\tconst tx = db.transaction(storeName, \"readonly\");\n\t\t\t\tconst store = tx.objectStore(storeName);\n\t\t\t\tconst request = store.openCursor();\n\t\t\t\tconst entries: Array<[key: string, value: TStorageValue]> = [];\n\n\t\t\t\ttx.oncomplete = () => resolve(entries);\n\t\t\t\ttx.onerror = () => reject(tx.error);\n\t\t\t\ttx.onabort = () => reject(tx.error);\n\n\t\t\t\trequest.onsuccess = () => {\n\t\t\t\t\tconst cursor = request.result;\n\t\t\t\t\tif (!cursor) return;\n\n\t\t\t\t\tentries.push([String(cursor.key), cursor.value as TStorageValue]);\n\t\t\t\t\tcursor.continue();\n\t\t\t\t};\n\t\t\t\trequest.onerror = () => reject(request.error);\n\t\t\t});\n\t\t}\n\t};\n}\n\nexport function createReactQueryPersister() {\n\tconst storage = createIndexedDbQueryStorage<PersistedQuery>();\n\tif (!storage) return undefined;\n\tconst indexedDbStorage = storage;\n\n\tconst publicPersister = experimental_createQueryPersister<PersistedQuery>({\n\t\tstorage: indexedDbStorage,\n\t\tserialize: (query) => query,\n\t\tdeserialize: (query) => query,\n\t\tprefix: getPersistencePrefix(),\n\t\tbuster: getQueryPersistenceProjectAdapter().persistenceBuster ?? REACT_QUERY_PERSISTENCE_BUSTER,\n\t\tmaxAge: REACT_QUERY_PERSISTENCE_MAX_AGE,\n\t\trefetchOnRestore: true,\n\t\tfilters: { predicate: shouldPersistQuery }\n\t});\n\tconst sessionPersisters = new Map<string, ReturnType<typeof experimental_createQueryPersister<PersistedQuery>>>();\n\n\tfunction getSessionPersister(scope: SessionQueryPersistenceScope, cacheName: string, maxEntries: number) {\n\t\tconst prefix = getSessionPersistenceCachePrefix(scope.id, cacheName);\n\t\tconst identity = `${prefix}:${maxEntries}:${scope.expiresAt}`;\n\t\tconst existing = sessionPersisters.get(identity);\n\t\tif (existing) return existing;\n\n\t\tlet pruneQueue = Promise.resolve();\n\t\tconst boundedStorage: AsyncStorage<PersistedQuery> = {\n\t\t\tgetItem: (key) => {\n\t\t\t\tconst activeScope = getSessionQueryPersistenceScope();\n\t\t\t\treturn activeScope?.id === scope.id ? indexedDbStorage.getItem(key) : undefined;\n\t\t\t},\n\t\t\tremoveItem: (key) => indexedDbStorage.removeItem(key),\n\t\t\tentries: () => indexedDbStorage.entries(),\n\t\t\tsetItem: async (key, value) => {\n\t\t\t\tconst activeScope = getSessionQueryPersistenceScope();\n\t\t\t\tif (activeScope?.id !== scope.id) return;\n\n\t\t\t\tawait indexedDbStorage.setItem(key, value);\n\t\t\t\tpruneQueue = pruneQueue.then(async () => {\n\t\t\t\t\tconst entries = (await indexedDbStorage.entries()).filter(([entryKey]) => entryKey.startsWith(`${prefix}-`));\n\t\t\t\t\tentries.sort((left, right) => (right[1].state.dataUpdatedAt ?? 0) - (left[1].state.dataUpdatedAt ?? 0));\n\t\t\t\t\tawait Promise.all(entries.slice(maxEntries).map(([entryKey]) => indexedDbStorage.removeItem(entryKey)));\n\t\t\t\t});\n\t\t\t\tawait pruneQueue;\n\t\t\t}\n\t\t};\n\t\tconst persister = experimental_createQueryPersister<PersistedQuery>({\n\t\t\tstorage: boundedStorage,\n\t\t\tserialize: (query) => query,\n\t\t\tdeserialize: (query) => query,\n\t\t\tprefix,\n\t\t\tbuster: getQueryPersistenceProjectAdapter().persistenceBuster ?? REACT_QUERY_PERSISTENCE_BUSTER,\n\t\t\tmaxAge: REACT_QUERY_PERSISTENCE_MAX_AGE,\n\t\t\trefetchOnRestore: true,\n\t\t\tfilters: {\n\t\t\t\tpredicate: (query) => readSessionQueryPersistencePolicy(query)?.cacheName === cacheName\n\t\t\t}\n\t\t});\n\t\tsessionPersisters.set(identity, persister);\n\t\treturn persister;\n\t}\n\n\treturn {\n\t\t...publicPersister,\n\t\tasync persisterFn<T, TQueryKey extends QueryKey>(\n\t\t\tqueryFn: QueryFunction<T, TQueryKey>,\n\t\t\tcontext: QueryFunctionContext<TQueryKey>,\n\t\t\tquery: Query\n\t\t): Promise<T> {\n\t\t\tconst policy = readSessionQueryPersistencePolicy(query);\n\t\t\tif (!policy) return publicPersister.persisterFn(queryFn, context, query);\n\n\t\t\tconst scope = getSessionQueryPersistenceScope();\n\t\t\tif (!scope) return Promise.resolve(queryFn(context));\n\n\t\t\treturn getSessionPersister(scope, policy.cacheName, policy.maxEntries).persisterFn(queryFn, context, query);\n\t\t}\n\t};\n}\n","import { type QueryMeta } from \"@tanstack/react-query\";\n\nexport type SessionQueryPersistencePolicy = {\n\t/** Устойчивое предметно независимое имя bounded-кэша внутри текущей сессии. */\n\treadonly cacheName: string;\n\t/** Максимальное число последних успешно полученных query в этом кэше. */\n\treadonly maxEntries: number;\n};\n\n/**\n * Общие признаки query, которые управляют инфраструктурой клиентского кеша.\n *\n * `persist` разрешает долговременное хранение публичных справочных данных,\n * а `sessionScoped` помечает данные, доступность или содержимое которых зависит\n * от текущей серверной сессии. Эти признаки нельзя совмещать. Отдельный\n * `sessionPersistence` разрешает bounded storage только внутри активного scope.\n */\nexport type AppQueryMeta = {\n\treadonly persist?: boolean;\n\treadonly sessionScoped?: boolean;\n\treadonly sessionPersistence?: SessionQueryPersistencePolicy;\n} & Record<string, unknown>;\n\ndeclare module \"@tanstack/react-query\" {\n\tinterface Register {\n\t\tqueryMeta: AppQueryMeta;\n\t}\n}\n\n/** Разрешает сохранять публичную query в долговременном хранилище. */\nexport const persistedQueryMeta = { persist: true } as const satisfies QueryMeta;\n\n/** Помечает query для сброса при изменении серверной сессии. */\nexport const sessionScopedQueryMeta = { sessionScoped: true } as const satisfies QueryMeta;\n\n/**\n * Разрешает bounded persistence только внутри явно установленной server-session identity.\n * Scope и его срок жизни задаются отдельно через `setSessionQueryPersistenceScope`,\n * поэтому query contract не хранит идентификатор пользователя или сессии.\n */\nexport function createSessionQueryPersistenceMeta(policy: SessionQueryPersistencePolicy) {\n\tconst cacheName = policy.cacheName.trim();\n\tif (!/^[a-z0-9][a-z0-9_-]{0,63}$/u.test(cacheName)) {\n\t\tthrow new Error(\"Имя session query cache должно содержать только строчные латинские буквы, цифры, _ и -\");\n\t}\n\tif (!Number.isSafeInteger(policy.maxEntries) || policy.maxEntries < 1 || policy.maxEntries > 5_000) {\n\t\tthrow new Error(\"Лимит session query cache должен быть целым числом от 1 до 5000\");\n\t}\n\n\treturn {\n\t\tsessionScoped: true,\n\t\tsessionPersistence: { cacheName, maxEntries: policy.maxEntries }\n\t} as const satisfies QueryMeta;\n}\n","import {\n\tDefaultError,\n\tMutation,\n\tMutationCache,\n\tMutationFunctionContext,\n\tQuery,\n\tQueryCache,\n\tQueryClient,\n\tQueryKey\n} from \"@tanstack/react-query\";\n\nimport { createReactQueryPersister } from \"./persistence\";\n\nexport type QueryBroadcastOptions = {\n\tonQueryError?: (error: DefaultError, query: Query<unknown, unknown, unknown>) => void;\n\tonMutationError?: (\n\t\terror: DefaultError,\n\t\tvariables: unknown,\n\t\tonMutateResult: unknown,\n\t\tmutation: Mutation<unknown, unknown, unknown>,\n\t\tcontext: MutationFunctionContext\n\t) => Promise<unknown> | unknown;\n};\n\n/**\n * Создаёт настроенный QueryClient. Singleton lifecycle обеспечивает host-приложение.\n */\nexport function createQueryClient({ onQueryError, onMutationError }: QueryBroadcastOptions) {\n\tconst queryPersister = createReactQueryPersister();\n\n\tconst queryClient = new QueryClient({\n\t\tqueryCache: new QueryCache({ onError: onQueryError }),\n\t\tmutationCache: new MutationCache({ onError: onMutationError }),\n\t\tdefaultOptions: {\n\t\t\t// TODO: Vite env\n\t\t\tqueries: {\n\t\t\t\tstaleTime: 10 * 60 * 1000,\n\t\t\t\tgcTime: 30 * 60 * 1000,\n\t\t\t\tthrowOnError: false,\n\t\t\t\tretry: false,\n\t\t\t\t...(queryPersister ? { persister: queryPersister.persisterFn } : {})\n\t\t\t},\n\t\t\tmutations: {\n\t\t\t\tthrowOnError: false,\n\t\t\t\tretry: false\n\t\t\t}\n\t\t}\n\t});\n\n\treturn queryClient;\n}\n\n/**\n * Контекст, возвращаемый из `onMutateOptimistic`.\n * Содержит снапшот кеша до оптимистичного обновления.\n * Передаётся в `onError`, `onSuccess`, `onSettled` через TanStack Query.\n */\nexport type OptimisticContext<TQueryCacheData> = {\n\tprevious: TQueryCacheData | undefined;\n};\n\n/**\n * _Оптимистичное обновление — это когда мы моментально меняем локальный кэш (и UI) до ответа сервера,\n * чтобы интерфейс был отзывчивым. Если сервер вернул ошибку — откатываем изменения.\n * Если сервер подтвердил — либо принимаем ответ сервера,\n * либо оставляем оптимистичное состояние (в зависимости от ситуации)._\n *\n * Обработчик `onMutate` для оптимистичного обновления.\n *\n * Выполняет три шага:\n * 1. Отменяет активные запросы по ключу — предотвращает гонку с сервером.\n * 2. Сохраняет снапшот текущего состояния кеша.\n * 3. Применяет оптимистичный патч через `patcher`.\n *\n * Возвращает {@link OptimisticContext} — передаётся в `onError` для отката.\n *\n * @example\n * // Обновление точечных данных в списке объектов\n * useMutation({\n * mutationFn: (data: SomeType) => { ... },\n * onMutate: async (data) =>\n * onMutateOptimistic(queryClient, someQueryKey, (old) => {\n * if (!old) return old;\n * return old.map((item) =>\n * item.id === data.id ? { ...item, visible: data.visible } : item\n * );\n * }),\n * onError: (_err, _vars, context) =>\n * onErrorOptimistic(queryClient, someQueryKey, context),\n * onSettled: () =>\n * onSettledOptimistic(queryClient, someQueryKey),\n * })\n */\nexport async function onMutateOptimistic<TQueryCacheData>(\n\tqc: QueryClient,\n\tqueryKey: QueryKey,\n\tpatcher: (oldCache: TQueryCacheData | undefined) => TQueryCacheData | undefined\n): Promise<OptimisticContext<TQueryCacheData>> {\n\t// 1) Отменяем запросы по ключу, чтобы не было гонки с сервером\n\tawait qc.cancelQueries({ queryKey });\n\n\t// 2) Сохраняем снапшот кеша для возможного отката\n\tconst previous = qc.getQueryData<TQueryCacheData>(queryKey);\n\n\t// 3) Применяем оптимистичный патч\n\tqc.setQueryData<TQueryCacheData>(queryKey, patcher);\n\n\treturn { previous };\n}\n\n/**\n * Обработчик `onError` для оптимистичного обновления.\n *\n * Откатывает кеш к снапшоту, сохранённому в {@link OptimisticContext}.\n * Если `context` не определён (например, `onMutate` сам выбросил ошибку) — безопасно ничего не делает.\n *\n * @example\n * onError: (_err, _vars, context) =>\n * onErrorOptimistic(queryClient, someQueryKey, context),\n */\nexport function onErrorOptimistic<TQueryCacheData>(\n\tqc: QueryClient,\n\tqueryKey: QueryKey,\n\tcontext: OptimisticContext<TQueryCacheData> | undefined\n): void {\n\tif (context?.previous !== undefined) {\n\t\tqc.setQueryData<TQueryCacheData>(queryKey, context.previous);\n\t}\n}\n\n/**\n * Обработчик `onSuccess` для оптимистичного обновления — применяет данные сервера в кеш.\n *\n * Используется, когда сервер вернул авторитетные данные, которые нужно слить\n * с текущим состоянием кеша (или заменить его целиком).\n *\n * Если сервер вернул `void` — эту функцию вызывать не нужно:\n * оптимистичное состояние в кеше остаётся без изменений.\n *\n * @param qc - экземпляр `QueryClient`\n * @param queryKey - ключ кеша\n * @param serverData - данные, вернувшиеся от сервера\n * @param merger - функция слияния: получает текущий кеш и ответ сервера, возвращает новое состояние\n *\n * @example\n * // Полная замена кеша ответом сервера\n * onSuccess: (data) =>\n * onSuccessOptimistic(queryClient, someQueryKey, data, (_cache, serverData) => serverData),\n *\n * @example\n * // Точечное обновление одного элемента в списке\n * onSuccess: (updatedItem) =>\n * onSuccessOptimistic(queryClient, someQueryKey, updatedItem,\n * (cache, item) => cache?.map((i) => (i.id === item.id ? item : i))\n * ),\n */\nexport function onSuccessOptimistic<TQueryCacheData, TServerData>(\n\tqc: QueryClient,\n\tqueryKey: QueryKey,\n\tserverData: TServerData,\n\tmerger: (cache: TQueryCacheData | undefined, serverData: TServerData) => TQueryCacheData | undefined\n): void {\n\tqc.setQueryData<TQueryCacheData>(queryKey, (old) => merger(old, serverData));\n}\n\n/**\n * Параметры для {@link onSettledOptimistic}.\n */\nexport type OnSettledOptimisticOptions = {\n\t/**\n\t * Тип рефетча при инвалидации.\n\t * - `\"none\"` (по умолчанию) — помечает кеш устаревшим, но не запускает рефетч немедленно.\n\t * - `\"active\"` — немедленно перезапрашивает активные подписчики.\n\t * - `\"inactive\"` — перезапрашивает неактивные (фоновые) запросы.\n\t * - `\"all\"` — перезапрашивает все подходящие запросы.\n\t */\n\trefetchType?: \"none\" | \"active\" | \"inactive\" | \"all\";\n};\n\n/**\n * Обработчик `onSettled` для оптимистичного обновления.\n *\n * Инвалидирует один или несколько query-ключей после завершения мутации\n * (выполняется и при успехе, и при ошибке).\n *\n * По умолчанию использует `refetchType: \"none\"` — помечает кеш устаревшим,\n * но не запускает немедленный рефетч. Это безопасный вариант при оптимистике:\n * данные в кеше уже актуальны (подтверждены или откатаны), а рефетч произойдёт\n * при следующем монтировании или явном запросе.\n *\n * Broadcast в другие вкладки — ответственность вызывающего кода через `broadcastCacheEvent`.\n *\n * @example\n * onSettled: () =>\n * onSettledOptimistic(queryClient, someQueryKey),\n *\n * @example\n * // С принудительным рефетчем активных подписчиков\n * onSettled: () =>\n * onSettledOptimistic(queryClient, [queryKeyA, queryKeyB], { refetchType: \"active\" }),\n */\nexport async function onSettledOptimistic(\n\tqc: QueryClient,\n\tqueryKeys: QueryKey | QueryKey[],\n\toptions?: OnSettledOptimisticOptions\n): Promise<void> {\n\tconst refetchType = options?.refetchType ?? \"none\";\n\t// Нормализуем одиночный ключ к массиву для единообразной обработки\n\tconst keys: QueryKey[] = isNestedQueryKey(queryKeys) ? queryKeys : [queryKeys];\n\n\tawait Promise.all(keys.map((queryKey) => qc.invalidateQueries({ queryKey, refetchType })));\n}\n\n/**\n * Определяет, является ли значение массивом query-ключей (QueryKey[]),\n * а не одиночным QueryKey.\n *\n * QueryKey в TanStack Query — это `string | readonly unknown[]`.\n * Чтобы отличить `QueryKey[]` от `QueryKey` типа `readonly unknown[]`,\n * проверяем, содержит ли массив хотя бы один вложенный массив или строку на верхнем уровне.\n */\nfunction isNestedQueryKey(value: QueryKey | QueryKey[]): value is QueryKey[] {\n\tif (!Array.isArray(value)) return false;\n\t// Если первый элемент сам является массивом — это QueryKey[]\n\treturn Array.isArray(value[0]);\n}\n","import { type Query, type QueryClient } from \"@tanstack/react-query\";\n\nconst SESSION_SCOPED_QUERY_CACHE_RESET_EVENT = \"session-scoped-query-cache-reset\";\n\ntype SessionScopedQueryCacheResetEvent = {\n\treadonly type: typeof SESSION_SCOPED_QUERY_CACHE_RESET_EVENT;\n};\n\nexport type ResetSessionScopedQueriesOptions = {\n\t/**\n\t * Передавать ли команду сброса другим вкладкам. При обработке входящего\n\t * сообщения значение отключается, чтобы вкладки не создавали цикл событий.\n\t */\n\treadonly broadcast?: boolean;\n};\n\nexport type InstallSessionScopedQueryResetOptions = {\n\t/** Уникальное для приложения имя служебного BroadcastChannel. */\n\treadonly channelName: string;\n};\n\nexport type InstalledSessionScopedQueryReset = {\n\t/** Освобождает канал и прекращает межвкладочную синхронизацию. */\n\treadonly cleanup: () => void;\n};\n\nlet broadcastSessionScopedQueryReset: (() => void) | undefined;\n\n/** Проверяет, относится ли query к данным текущей серверной сессии. */\nexport function isSessionScopedQuery(query: Pick<Query, \"meta\">): boolean {\n\treturn query.meta?.sessionScoped === true;\n}\n\n/**\n * Сбрасывает все session-scoped query после входа, выхода, обновления сессии\n * или потери авторизации.\n *\n * Сначала отменяются незавершённые запросы старой сессии, затем TanStack Query\n * очищает их состояние и повторно запрашивает только активные query. Обычные\n * публичные данные при этом остаются в кеше. По умолчанию отдельная команда\n * сброса отправляется другим вкладкам без передачи содержимого кеша или токенов.\n */\nexport async function resetSessionScopedQueries(queryClient: QueryClient, options: ResetSessionScopedQueriesOptions = {}): Promise<void> {\n\tconst filters = { predicate: isSessionScopedQuery } as const;\n\n\tawait queryClient.cancelQueries(filters);\n\n\tconst matchedQueries = queryClient.getQueryCache().findAll(filters);\n\tfor (const query of matchedQueries) {\n\t\tif (query.getObserversCount() === 0) {\n\t\t\tqueryClient.getQueryCache().remove(query);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Hydrated query хранит SSR-данные как initial state, поэтому одного\n\t\t// query.reset() недостаточно: старая сессия могла бы вернуться в UI.\n\t\tquery.reset();\n\t\tquery.setState({ data: undefined, dataUpdatedAt: 0 });\n\t}\n\n\tawait queryClient.refetchQueries(\n\t\t{\n\t\t\tpredicate: (query) => isSessionScopedQuery(query) && query.getObserversCount() > 0,\n\t\t\ttype: \"active\"\n\t\t},\n\t\t{ cancelRefetch: true }\n\t);\n\n\tif (options.broadcast !== false) {\n\t\tbroadcastSessionScopedQueryReset?.();\n\t}\n}\n\n/**\n * Подключает безопасную межвкладочную синхронизацию смены сессии.\n *\n * Канал передаёт только команду сброса. В отличие от полной синхронизации\n * Query cache, он не публикует данные query и потому подходит для auth-зависимых\n * ответов. На сервере и в браузерах без BroadcastChannel возвращается no-op.\n */\nexport function installSessionScopedQueryReset(\n\tqueryClient: QueryClient,\n\toptions: InstallSessionScopedQueryResetOptions\n): InstalledSessionScopedQueryReset {\n\tif (typeof window === \"undefined\" || typeof BroadcastChannel === \"undefined\") {\n\t\treturn { cleanup: () => undefined };\n\t}\n\n\tconst channel = new BroadcastChannel(options.channelName);\n\tconst broadcast = () => {\n\t\tconst event: SessionScopedQueryCacheResetEvent = {\n\t\t\ttype: SESSION_SCOPED_QUERY_CACHE_RESET_EVENT\n\t\t};\n\t\tchannel.postMessage(event);\n\t};\n\n\tbroadcastSessionScopedQueryReset = broadcast;\n\tchannel.onmessage = (event: MessageEvent<unknown>) => {\n\t\tif (!isSessionScopedQueryCacheResetEvent(event.data)) return;\n\t\tvoid resetSessionScopedQueries(queryClient, { broadcast: false });\n\t};\n\n\treturn {\n\t\tcleanup: () => {\n\t\t\tchannel.onmessage = null;\n\t\t\tchannel.close();\n\t\t\tif (broadcastSessionScopedQueryReset === broadcast) {\n\t\t\t\tbroadcastSessionScopedQueryReset = undefined;\n\t\t\t}\n\t\t}\n\t};\n}\n\n/** Защищает обработчик канала от посторонних или устаревших сообщений. */\nfunction isSessionScopedQueryCacheResetEvent(value: unknown): value is SessionScopedQueryCacheResetEvent {\n\treturn typeof value === \"object\" && value !== null && \"type\" in value && value.type === SESSION_SCOPED_QUERY_CACHE_RESET_EVENT;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/query-client/broadcast.ts","../../src/query-client/invalidationBroadcast.ts","../../src/query-client/queryPersistenceAdapter.ts","../../src/query-client/persistence.ts","../../src/query-client/queryMeta.ts","../../src/query-client/queryClient.ts","../../src/query-client/sessionScoped.ts"],"sourcesContent":["import { broadcastQueryClient } from \"@tanstack/query-broadcast-client-experimental\";\n\nimport { uuidv4 } from \"../crypto\";\nimport { isRecord } from \"../validators\";\n\nimport type { QueryClient, QueryKey } from \"@tanstack/react-query\";\n\ntype CacheSyncEvent =\n\t| { type: \"invalidate\"; keys: QueryKey[]; refetchType?: \"none\" | \"all\" | \"active\" | \"inactive\" }\n\t| { type: \"setQueryData\"; key: QueryKey; data: unknown };\n\ntype InstallBroadcastOptions = {\n\tqueriesChannel: string;\n\teventsChannel: string;\n};\n\ntype InstalledReactQueryBroadcast = {\n\tbroadcast: (event: CacheSyncEvent) => void;\n\tcleanup: () => void;\n};\n\ntype WireMessage = {\n\ttabId: string;\n\tevent: CacheSyncEvent;\n};\n\nlet broadcastFn: ((event: CacheSyncEvent) => void) | null = null;\n\nexport function setBroadcastFn(fn: (event: CacheSyncEvent) => void) {\n\tbroadcastFn = fn;\n}\n\nexport function broadcastCacheEvent(event: CacheSyncEvent) {\n\tbroadcastFn?.(event);\n}\n\nexport function installReactQueryBroadcast(queryClient: QueryClient, opts: InstallBroadcastOptions): InstalledReactQueryBroadcast {\n\tif (typeof window === \"undefined\") {\n\t\treturn { broadcast: () => {}, cleanup: () => {} };\n\t}\n\n\tlet isApplyingRemoteEvent = false;\n\n\t// 1) Sync query cache (data/state)\n\tconst cleanupQueryBroadcast = broadcastQueryClient({\n\t\tqueryClient,\n\t\tbroadcastChannel: opts.queriesChannel\n\t});\n\n\t// 2) Sync \"events\" (invalidate/setQueryData)\n\tconst tabId = uuidv4();\n\tconst bc = new BroadcastChannel(opts.eventsChannel);\n\tconst unsubscribeInvalidationBroadcast = queryClient.getQueryCache().subscribe((queryEvent) => {\n\t\tif (isApplyingRemoteEvent) return;\n\t\tif (queryEvent.type !== \"updated\" || queryEvent.action.type !== \"invalidate\") return;\n\n\t\tbroadcast({\n\t\t\ttype: \"invalidate\",\n\t\t\tkeys: [queryEvent.query.queryKey],\n\t\t\trefetchType: \"none\"\n\t\t});\n\t});\n\n\tbc.onmessage = (ev: MessageEvent) => {\n\t\t// MessageEvent.data: unknown -> делаем безопасное сужение без any\n\t\tconst data: unknown = ev.data;\n\t\tif (!isWireMessage(data)) return;\n\t\tif (data.tabId === tabId) return;\n\n\t\tconst e = data.event;\n\t\tisApplyingRemoteEvent = true;\n\n\t\ttry {\n\t\t\tif (e.type === \"invalidate\") {\n\t\t\t\tconst refetchType = e.refetchType ?? \"none\";\n\t\t\t\tfor (const key of e.keys) {\n\t\t\t\t\tqueryClient.invalidateQueries({ queryKey: key, refetchType });\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (e.type === \"setQueryData\") {\n\t\t\t\tqueryClient.setQueryData(e.key, e.data);\n\t\t\t\treturn;\n\t\t\t}\n\t\t} finally {\n\t\t\tisApplyingRemoteEvent = false;\n\t\t}\n\t};\n\n\tfunction broadcast(event: CacheSyncEvent) {\n\t\tconst msg: WireMessage = { tabId, event };\n\t\tbc.postMessage(msg);\n\t}\n\n\treturn {\n\t\tbroadcast,\n\t\tcleanup: () => {\n\t\t\tunsubscribeInvalidationBroadcast();\n\t\t\tcleanupQueryBroadcast();\n\t\t\tbc.close();\n\t\t}\n\t};\n}\n\nfunction isWireMessage(v: unknown): v is WireMessage {\n\tif (!isRecord(v)) return false;\n\tif (typeof v.tabId !== \"string\") return false;\n\tif (!(\"event\" in v)) return false;\n\treturn isCacheSyncEvent(v.event);\n}\n\nfunction isCacheSyncEvent(v: unknown): v is CacheSyncEvent {\n\tif (!isRecord(v)) return false;\n\tif (v.type === \"invalidate\") {\n\t\t// keys: QueryKey[] — runtime мы не сможем валидировать “идеально”,\n\t\t// но проверим что это массив, а refetchType — допустимое значение\n\t\tif (!Array.isArray(v.keys)) return false;\n\t\tif (v.refetchType === undefined) return true;\n\t\treturn v.refetchType === \"none\" || v.refetchType === \"active\" || v.refetchType === \"inactive\" || v.refetchType === \"all\";\n\t}\n\tif (v.type === \"setQueryData\") {\n\t\t// key должен быть массивом или строкой — QueryKey в рантайме обычно массив\n\t\t// проверим хотя бы наличие поля\n\t\treturn \"key\" in v && \"data\" in v;\n\t}\n\treturn false;\n}\n","import { uuidv4 } from \"../crypto\";\nimport { isRecord } from \"../validators\";\n\nimport type { QueryClient, QueryKey } from \"@tanstack/react-query\";\n\nexport type QueryInvalidationBroadcastOptions = {\n\t/** Уникальное в пределах origin имя канала приложения. */\n\treadonly channelName: string;\n\t/** Ограничивает как исходящие, так и входящие Query keys. */\n\treadonly matchesQuery: (queryKey: QueryKey) => boolean;\n\t/** Определяет, какие queries получающей вкладки будут перезапрошены сразу. */\n\treadonly refetchType?: \"none\" | \"active\" | \"inactive\" | \"all\";\n};\n\nexport type InstalledQueryInvalidationBroadcast = {\n\t/** Явно передаёт точечную инвалидацию без изменения локального QueryClient. */\n\treadonly broadcast: (queryKey: QueryKey) => void;\n\treadonly cleanup: () => void;\n};\n\ntype QueryInvalidationWireMessage = {\n\treadonly type: \"query.invalidate\";\n\treadonly senderId: string;\n\treadonly queryKey: QueryKey;\n};\n\nfunction isQueryInvalidationWireMessage(value: unknown): value is QueryInvalidationWireMessage {\n\treturn isRecord(value) && value.type === \"query.invalidate\" && typeof value.senderId === \"string\" && Array.isArray(value.queryKey);\n}\n\n/**\n * Передаёт между вкладками только факт инвалидации выбранного Query key.\n * Server data остаётся у QueryClient каждой вкладки и заново читается её queryFn.\n */\nexport function installQueryInvalidationBroadcast(\n\tqueryClient: QueryClient,\n\toptions: QueryInvalidationBroadcastOptions\n): InstalledQueryInvalidationBroadcast {\n\tif (typeof window === \"undefined\" || typeof BroadcastChannel === \"undefined\") {\n\t\treturn { broadcast: () => undefined, cleanup: () => undefined };\n\t}\n\n\tconst senderId = uuidv4();\n\tconst channel = new BroadcastChannel(options.channelName);\n\tlet applyingRemoteInvalidation = false;\n\tlet closed = false;\n\n\tconst broadcast = (queryKey: QueryKey) => {\n\t\tif (closed || !options.matchesQuery(queryKey)) return;\n\t\tchannel.postMessage({ type: \"query.invalidate\", senderId, queryKey } satisfies QueryInvalidationWireMessage);\n\t};\n\n\tconst unsubscribe = queryClient.getQueryCache().subscribe((event) => {\n\t\tif (applyingRemoteInvalidation || event.type !== \"updated\" || event.action.type !== \"invalidate\") return;\n\t\tbroadcast(event.query.queryKey);\n\t});\n\n\tchannel.onmessage = (event: MessageEvent<unknown>) => {\n\t\tconst message = event.data;\n\t\tif (closed || !isQueryInvalidationWireMessage(message) || message.senderId === senderId || !options.matchesQuery(message.queryKey))\n\t\t\treturn;\n\n\t\tapplyingRemoteInvalidation = true;\n\t\ttry {\n\t\t\tvoid queryClient.invalidateQueries({\n\t\t\t\tqueryKey: message.queryKey,\n\t\t\t\texact: true,\n\t\t\t\trefetchType: options.refetchType ?? \"active\"\n\t\t\t});\n\t\t} finally {\n\t\t\tapplyingRemoteInvalidation = false;\n\t\t}\n\t};\n\n\treturn {\n\t\tbroadcast,\n\t\tcleanup() {\n\t\t\tif (closed) return;\n\t\t\tclosed = true;\n\t\t\tunsubscribe();\n\t\t\tchannel.close();\n\t\t}\n\t};\n}\n","export type QueryPersistenceProjectAdapter = {\n\tpersistenceBuster?: string;\n\tresolveSystemIdentifier?: () => string | null | undefined;\n};\n\nlet queryPersistenceProjectAdapter: QueryPersistenceProjectAdapter = {};\n\nexport function configureQueryPersistenceProjectAdapter(adapter: QueryPersistenceProjectAdapter) {\n\tqueryPersistenceProjectAdapter = adapter;\n}\n\nexport function getQueryPersistenceProjectAdapter() {\n\treturn queryPersistenceProjectAdapter;\n}\n","import { experimental_createQueryPersister, type AsyncStorage, type PersistedQuery } from \"@tanstack/query-persist-client-core\";\nimport { type Query, type QueryFunction, type QueryFunctionContext, type QueryKey } from \"@tanstack/react-query\";\n\nimport { getQueryPersistenceProjectAdapter } from \"./queryPersistenceAdapter\";\n\nconst STORE_NAME = \"queries\";\nconst SESSION_PERSISTENCE_MARKER = \"session\";\n\nexport const REACT_QUERY_PERSISTENCE_BUSTER = __REACT_QUERY_PERSISTENCE_BUSTER__;\nexport const REACT_QUERY_PERSISTENCE_MAX_AGE = 90 * 24 * 60 * 60 * 1000;\n\ntype IndexedDbQueryStorageOptions = {\n\tdbName?: string;\n\tstoreName?: string;\n\tindexedDB?: IDBFactory;\n};\n\nexport type SessionQueryPersistenceScope = {\n\t/** Публичная identity серверной сессии; cookie secret или access token здесь недопустимы. */\n\treadonly id: string;\n\t/** Абсолютная граница, после которой snapshots этой сессии нельзя читать или записывать. */\n\treadonly expiresAt: string;\n};\n\nlet activeSessionPersistenceScope: SessionQueryPersistenceScope | null = null;\n\n/**\n * Описывает итог синхронного преобразования записи внутри одной IndexedDB-транзакции.\n * Отдельный result позволяет вернуть вызывающему коду вычисленный снимок только после commit.\n */\nexport type IndexedDbAtomicUpdateDecision<TStorageValue, TResult> =\n\t| { readonly action: \"keep\"; readonly result: TResult }\n\t| { readonly action: \"set\"; readonly value: TStorageValue; readonly result: TResult }\n\t| { readonly action: \"remove\"; readonly result: TResult };\n\nexport type IndexedDbQueryStorage<TStorageValue> = AsyncStorage<TStorageValue> & {\n\t/** Перечисляет записи object store для garbage collection и bounded persistence. */\n\treadonly entries: () => Promise<Array<[key: string, value: TStorageValue]>>;\n\t/**\n\t * Читает и изменяет одну запись в общей readwrite-транзакции, поэтому параллельные\n\t * вкладки не могут потерять уже зафиксированное изменение между get и set.\n\t * Callback должен быть синхронным: IndexedDB закрывает неактивную транзакцию до\n\t * завершения произвольной асинхронной работы.\n\t */\n\treadonly updateItem: <TResult>(\n\t\tkey: string,\n\t\tupdate: (value: TStorageValue | undefined) => IndexedDbAtomicUpdateDecision<TStorageValue, TResult>\n\t) => Promise<TResult>;\n};\n\nfunction resolveIndexedDbFactory(factory?: IDBFactory) {\n\tif (factory) return factory;\n\tif (typeof globalThis.indexedDB === \"undefined\") return undefined;\n\treturn globalThis.indexedDB;\n}\n\nfunction requestToPromise<T>(request: IDBRequest<T>) {\n\treturn new Promise<T>((resolve, reject) => {\n\t\trequest.onsuccess = () => resolve(request.result);\n\t\trequest.onerror = () => reject(request.error);\n\t});\n}\n\nfunction getSystemIdentifier() {\n\tif (__DEV__) {\n\t\treturn `dev:${__APP_ID__}`;\n\t}\n\n\tconst projectIdentifier = getQueryPersistenceProjectAdapter().resolveSystemIdentifier?.();\n\tif (projectIdentifier) return projectIdentifier;\n\n\tconst hostname = typeof window === \"undefined\" ? \"server\" : window.location.hostname;\n\treturn `${__APP_ID__}:${hostname}`;\n}\n\nfunction getPersistencePrefix() {\n\tconst system = getSystemIdentifier();\n\treturn `${system}`;\n}\n\nfunction encodePersistenceSegment(value: string) {\n\treturn encodeURIComponent(value);\n}\n\nfunction getSessionPersistenceRootPrefix() {\n\treturn `${getPersistencePrefix()}-${SESSION_PERSISTENCE_MARKER}-`;\n}\n\nfunction getSessionPersistenceScopePrefix(scopeId: string) {\n\treturn `${getSessionPersistenceRootPrefix()}${encodePersistenceSegment(scopeId)}-`;\n}\n\nfunction getSessionPersistenceCachePrefix(scopeId: string, cacheName: string) {\n\treturn `${getSessionPersistenceScopePrefix(scopeId)}${encodePersistenceSegment(cacheName)}`;\n}\n\nfunction isSessionPersistenceScopeValid(scope: SessionQueryPersistenceScope | null): scope is SessionQueryPersistenceScope {\n\treturn (\n\t\tBoolean(scope?.id.trim()) && Number.isFinite(Date.parse(scope?.expiresAt ?? \"\")) && Date.parse(scope?.expiresAt ?? \"\") > Date.now()\n\t);\n}\n\n/** Возвращает текущую browser-session identity, которой разрешено читать приватные persisted query. */\nexport function getSessionQueryPersistenceScope(): SessionQueryPersistenceScope | null {\n\treturn isSessionPersistenceScopeValid(activeSessionPersistenceScope) ? activeSessionPersistenceScope : null;\n}\n\nasync function removeSessionPersistenceEntries(predicate: (key: string) => boolean) {\n\tconst storage = createIndexedDbQueryStorage<PersistedQuery>();\n\tif (!storage?.entries) return;\n\n\tconst entries = await storage.entries();\n\tawait Promise.all(entries.filter(([key]) => predicate(key)).map(([key]) => storage.removeItem(key)));\n}\n\n/** Удаляет persisted query одной сессии или всех сессий текущего приложения. */\nexport async function clearSessionQueryPersistence(scopeId?: string): Promise<void> {\n\tconst prefix = scopeId === undefined ? getSessionPersistenceRootPrefix() : getSessionPersistenceScopePrefix(scopeId);\n\tawait removeSessionPersistenceEntries((key) => key.startsWith(prefix));\n}\n\n/**\n * Устанавливает единственную активную server-session identity и сразу исключает\n * чтение старого scope. Browser storage очищается асинхронно уже после смены\n * in-memory границы, поэтому параллельный query не может записаться старому пользователю.\n */\nexport async function setSessionQueryPersistenceScope(scope: SessionQueryPersistenceScope | null): Promise<void> {\n\tconst normalized = scope && isSessionPersistenceScopeValid(scope) ? { id: scope.id.trim(), expiresAt: scope.expiresAt } : null;\n\tactiveSessionPersistenceScope = normalized;\n\n\tif (!normalized) {\n\t\tawait clearSessionQueryPersistence();\n\t\treturn;\n\t}\n\n\tconst activePrefix = getSessionPersistenceScopePrefix(normalized.id);\n\tawait removeSessionPersistenceEntries((key) => key.startsWith(getSessionPersistenceRootPrefix()) && !key.startsWith(activePrefix));\n}\n\nexport function shouldPersistQuery(query: Pick<Query, \"meta\">) {\n\treturn query.meta?.persist === true && query.meta.sessionScoped !== true;\n}\n\nfunction readSessionQueryPersistencePolicy(query: Pick<Query, \"meta\">) {\n\tconst policy = query.meta?.sessionPersistence;\n\tif (query.meta?.sessionScoped !== true || !policy || typeof policy !== \"object\") return null;\n\tif (!(\"cacheName\" in policy) || !(\"maxEntries\" in policy)) return null;\n\tif (typeof policy.cacheName !== \"string\" || !/^[a-z0-9][a-z0-9_-]{0,63}$/u.test(policy.cacheName)) return null;\n\tif (!Number.isSafeInteger(policy.maxEntries) || policy.maxEntries < 1 || policy.maxEntries > 5_000) return null;\n\tconst refetchOnRestore = \"refetchOnRestore\" in policy ? policy.refetchOnRestore : undefined;\n\tif (refetchOnRestore !== undefined && typeof refetchOnRestore !== \"boolean\") return null;\n\tconst monotonicRevisionField = \"monotonicRevisionField\" in policy ? policy.monotonicRevisionField : undefined;\n\tif (\n\t\tmonotonicRevisionField !== undefined &&\n\t\t(typeof monotonicRevisionField !== \"string\" || !/^[A-Za-z][A-Za-z0-9_]{0,63}$/u.test(monotonicRevisionField))\n\t) {\n\t\treturn null;\n\t}\n\treturn { cacheName: policy.cacheName, maxEntries: policy.maxEntries, refetchOnRestore, monotonicRevisionField } as const;\n}\n\nfunction readMonotonicDecimalRevision(query: PersistedQuery, field: string): string | null {\n\tconst data = query.state.data;\n\tif (typeof data !== \"object\" || data === null || Array.isArray(data)) return null;\n\tconst revision = Reflect.get(data, field);\n\tif (typeof revision !== \"string\" || !/^\\d+$/u.test(revision)) return null;\n\treturn revision.replace(/^0+(?=\\d)/u, \"\");\n}\n\nfunction compareDecimalRevisions(left: string, right: string): number {\n\tif (left.length !== right.length) return left.length - right.length;\n\tif (left === right) return 0;\n\treturn left < right ? -1 : 1;\n}\n\nexport function createIndexedDbQueryStorage<TStorageValue = unknown>(\n\toptions: IndexedDbQueryStorageOptions = {}\n): IndexedDbQueryStorage<TStorageValue> | undefined {\n\tconst factory = resolveIndexedDbFactory(options.indexedDB);\n\tif (!factory) return undefined;\n\n\tconst idb = factory;\n\tconst dbName = options.dbName ?? getSystemIdentifier();\n\tconst storeName = options.storeName ?? STORE_NAME;\n\tlet dbPromise: Promise<IDBDatabase> | null = null;\n\n\tfunction openDb() {\n\t\tif (dbPromise) return dbPromise;\n\n\t\tdbPromise = new Promise<IDBDatabase>((resolve, reject) => {\n\t\t\tconst request = idb.open(dbName, 1);\n\n\t\t\trequest.onupgradeneeded = () => {\n\t\t\t\tconst db = request.result;\n\t\t\t\tif (!db.objectStoreNames.contains(storeName)) {\n\t\t\t\t\tdb.createObjectStore(storeName);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\trequest.onsuccess = () => resolve(request.result);\n\t\t\trequest.onerror = () => reject(request.error);\n\t\t\trequest.onblocked = () => reject(request.error ?? new Error(\"Открытие IndexedDB заблокировано\"));\n\t\t});\n\n\t\treturn dbPromise;\n\t}\n\n\tasync function runTransaction<T>(mode: IDBTransactionMode, action: (store: IDBObjectStore) => IDBRequest<T>) {\n\t\tconst db = await openDb();\n\t\tconst tx = db.transaction(storeName, mode);\n\t\tconst store = tx.objectStore(storeName);\n\t\tconst request = action(store);\n\t\treturn requestToPromise(request);\n\t}\n\n\tasync function runWrite(mode: IDBTransactionMode, action: (store: IDBObjectStore) => void) {\n\t\tconst db = await openDb();\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst tx = db.transaction(storeName, mode);\n\t\t\tconst store = tx.objectStore(storeName);\n\n\t\t\ttx.oncomplete = () => resolve();\n\t\t\ttx.onerror = () => reject(tx.error);\n\t\t\ttx.onabort = () => reject(tx.error);\n\n\t\t\ttry {\n\t\t\t\taction(store);\n\t\t\t} catch (error) {\n\t\t\t\ttx.abort();\n\t\t\t\treject(error);\n\t\t\t}\n\t\t});\n\t}\n\n\tasync function updateItem<TResult>(\n\t\tkey: string,\n\t\tupdate: (value: TStorageValue | undefined) => IndexedDbAtomicUpdateDecision<TStorageValue, TResult>\n\t) {\n\t\tconst db = await openDb();\n\t\treturn new Promise<TResult>((resolve, reject) => {\n\t\t\tconst tx = db.transaction(storeName, \"readwrite\");\n\t\t\tconst store = tx.objectStore(storeName);\n\t\t\tconst request = store.get(key);\n\t\t\tlet result: TResult;\n\t\t\tlet hasResult = false;\n\n\t\t\ttx.oncomplete = () => {\n\t\t\t\tif (hasResult) resolve(result);\n\t\t\t\telse reject(new Error(\"IndexedDB-транзакция завершилась без результата обновления\"));\n\t\t\t};\n\t\t\ttx.onerror = () => reject(tx.error);\n\t\t\ttx.onabort = () => reject(tx.error);\n\t\t\trequest.onerror = () => {\n\t\t\t\ttx.abort();\n\t\t\t\treject(request.error);\n\t\t\t};\n\t\t\trequest.onsuccess = () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst decision = update(request.result as TStorageValue | undefined);\n\t\t\t\t\tresult = decision.result;\n\t\t\t\t\thasResult = true;\n\t\t\t\t\tif (decision.action === \"set\") store.put(decision.value, key);\n\t\t\t\t\telse if (decision.action === \"remove\") store.delete(key);\n\t\t\t\t} catch (error) {\n\t\t\t\t\ttx.abort();\n\t\t\t\t\treject(error);\n\t\t\t\t}\n\t\t\t};\n\t\t});\n\t}\n\n\treturn {\n\t\tgetItem: (key) => runTransaction<TStorageValue | undefined>(\"readonly\", (store) => store.get(key)),\n\t\tsetItem: (key, value) => runWrite(\"readwrite\", (store) => store.put(value, key)),\n\t\tremoveItem: (key) => runWrite(\"readwrite\", (store) => store.delete(key)),\n\t\tupdateItem,\n\t\tentries: async () => {\n\t\t\tconst db = await openDb();\n\n\t\t\treturn new Promise<Array<[key: string, value: TStorageValue]>>((resolve, reject) => {\n\t\t\t\tconst tx = db.transaction(storeName, \"readonly\");\n\t\t\t\tconst store = tx.objectStore(storeName);\n\t\t\t\tconst request = store.openCursor();\n\t\t\t\tconst entries: Array<[key: string, value: TStorageValue]> = [];\n\n\t\t\t\ttx.oncomplete = () => resolve(entries);\n\t\t\t\ttx.onerror = () => reject(tx.error);\n\t\t\t\ttx.onabort = () => reject(tx.error);\n\n\t\t\t\trequest.onsuccess = () => {\n\t\t\t\t\tconst cursor = request.result;\n\t\t\t\t\tif (!cursor) return;\n\n\t\t\t\t\tentries.push([String(cursor.key), cursor.value as TStorageValue]);\n\t\t\t\t\tcursor.continue();\n\t\t\t\t};\n\t\t\t\trequest.onerror = () => reject(request.error);\n\t\t\t});\n\t\t}\n\t};\n}\n\nexport function createReactQueryPersister() {\n\tconst storage = createIndexedDbQueryStorage<PersistedQuery>();\n\tif (!storage) return undefined;\n\tconst indexedDbStorage = storage;\n\n\tconst publicPersister = experimental_createQueryPersister<PersistedQuery>({\n\t\tstorage: indexedDbStorage,\n\t\tserialize: (query) => query,\n\t\tdeserialize: (query) => query,\n\t\tprefix: getPersistencePrefix(),\n\t\tbuster: getQueryPersistenceProjectAdapter().persistenceBuster ?? REACT_QUERY_PERSISTENCE_BUSTER,\n\t\tmaxAge: REACT_QUERY_PERSISTENCE_MAX_AGE,\n\t\trefetchOnRestore: true,\n\t\tfilters: { predicate: shouldPersistQuery }\n\t});\n\tconst sessionPersisters = new Map<string, ReturnType<typeof experimental_createQueryPersister<PersistedQuery>>>();\n\n\tfunction getSessionPersister(\n\t\tscope: SessionQueryPersistenceScope,\n\t\tcacheName: string,\n\t\tmaxEntries: number,\n\t\trefetchOnRestore = true,\n\t\tmonotonicRevisionField?: string\n\t) {\n\t\tconst prefix = getSessionPersistenceCachePrefix(scope.id, cacheName);\n\t\tconst identity = `${prefix}:${maxEntries}:${refetchOnRestore}:${monotonicRevisionField ?? \"\"}:${scope.expiresAt}`;\n\t\tconst existing = sessionPersisters.get(identity);\n\t\tif (existing) return existing;\n\n\t\tlet pruneQueue = Promise.resolve();\n\t\tconst boundedStorage: AsyncStorage<PersistedQuery> = {\n\t\t\tgetItem: (key) => {\n\t\t\t\tconst activeScope = getSessionQueryPersistenceScope();\n\t\t\t\treturn activeScope?.id === scope.id ? indexedDbStorage.getItem(key) : undefined;\n\t\t\t},\n\t\t\tremoveItem: (key) => indexedDbStorage.removeItem(key),\n\t\t\tentries: () => indexedDbStorage.entries(),\n\t\t\tsetItem: async (key, value) => {\n\t\t\t\tconst activeScope = getSessionQueryPersistenceScope();\n\t\t\t\tif (activeScope?.id !== scope.id) return;\n\n\t\t\t\tif (monotonicRevisionField === undefined) {\n\t\t\t\t\tawait indexedDbStorage.setItem(key, value);\n\t\t\t\t} else {\n\t\t\t\t\tawait indexedDbStorage.updateItem(key, (stored) => {\n\t\t\t\t\t\tif (!stored || stored.buster !== value.buster) return { action: \"set\", value, result: undefined };\n\t\t\t\t\t\tconst storedRevision = readMonotonicDecimalRevision(stored, monotonicRevisionField);\n\t\t\t\t\t\tconst nextRevision = readMonotonicDecimalRevision(value, monotonicRevisionField);\n\t\t\t\t\t\tif (storedRevision !== null && nextRevision !== null && compareDecimalRevisions(storedRevision, nextRevision) > 0) {\n\t\t\t\t\t\t\treturn { action: \"keep\", result: undefined };\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn { action: \"set\", value, result: undefined };\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tpruneQueue = pruneQueue.then(async () => {\n\t\t\t\t\tconst entries = (await indexedDbStorage.entries()).filter(([entryKey]) => entryKey.startsWith(`${prefix}-`));\n\t\t\t\t\tentries.sort((left, right) => (right[1].state.dataUpdatedAt ?? 0) - (left[1].state.dataUpdatedAt ?? 0));\n\t\t\t\t\tawait Promise.all(entries.slice(maxEntries).map(([entryKey]) => indexedDbStorage.removeItem(entryKey)));\n\t\t\t\t});\n\t\t\t\tawait pruneQueue;\n\t\t\t}\n\t\t};\n\t\tconst persister = experimental_createQueryPersister<PersistedQuery>({\n\t\t\tstorage: boundedStorage,\n\t\t\tserialize: (query) => query,\n\t\t\tdeserialize: (query) => query,\n\t\t\tprefix,\n\t\t\tbuster: getQueryPersistenceProjectAdapter().persistenceBuster ?? REACT_QUERY_PERSISTENCE_BUSTER,\n\t\t\tmaxAge: REACT_QUERY_PERSISTENCE_MAX_AGE,\n\t\t\trefetchOnRestore,\n\t\t\tfilters: {\n\t\t\t\tpredicate: (query) => readSessionQueryPersistencePolicy(query)?.cacheName === cacheName\n\t\t\t}\n\t\t});\n\t\tsessionPersisters.set(identity, persister);\n\t\treturn persister;\n\t}\n\n\treturn {\n\t\t...publicPersister,\n\t\tasync persisterFn<T, TQueryKey extends QueryKey>(\n\t\t\tqueryFn: QueryFunction<T, TQueryKey>,\n\t\t\tcontext: QueryFunctionContext<TQueryKey>,\n\t\t\tquery: Query\n\t\t): Promise<T> {\n\t\t\tconst policy = readSessionQueryPersistencePolicy(query);\n\t\t\tif (!policy) return publicPersister.persisterFn(queryFn, context, query);\n\n\t\t\tconst scope = getSessionQueryPersistenceScope();\n\t\t\tif (!scope) return Promise.resolve(queryFn(context));\n\n\t\t\treturn getSessionPersister(\n\t\t\t\tscope,\n\t\t\t\tpolicy.cacheName,\n\t\t\t\tpolicy.maxEntries,\n\t\t\t\tpolicy.refetchOnRestore,\n\t\t\t\tpolicy.monotonicRevisionField\n\t\t\t).persisterFn(queryFn, context, query);\n\t\t}\n\t};\n}\n","import { type QueryMeta } from \"@tanstack/react-query\";\n\nexport type SessionQueryPersistencePolicy = {\n\t/** Устойчивое предметно независимое имя bounded-кэша внутри текущей сессии. */\n\treadonly cacheName: string;\n\t/** Максимальное число последних успешно полученных query в этом кэше. */\n\treadonly maxEntries: number;\n\t/**\n\t * По умолчанию восстановленный snapshot сразу перепроверяется полным queryFn.\n\t * `false` допустим только при наличии отдельной лёгкой проверки актуальности.\n\t */\n\treadonly refetchOnRestore?: boolean;\n\t/**\n\t * Поле верхнего уровня query data с неотрицательной целочисленной ревизией.\n\t * При наличии поля IndexedDB атомарно отклоняет запоздавшую запись меньшей\n\t * ревизии, поэтому параллельная вкладка не может откатить сохранённый snapshot.\n\t */\n\treadonly monotonicRevisionField?: string;\n};\n\n/**\n * Общие признаки query, которые управляют инфраструктурой клиентского кеша.\n *\n * `persist` разрешает долговременное хранение публичных справочных данных,\n * а `sessionScoped` помечает данные, доступность или содержимое которых зависит\n * от текущей серверной сессии. Эти признаки нельзя совмещать. Отдельный\n * `sessionPersistence` разрешает bounded storage только внутри активного scope.\n */\nexport type AppQueryMeta = {\n\treadonly persist?: boolean;\n\treadonly sessionScoped?: boolean;\n\treadonly sessionPersistence?: SessionQueryPersistencePolicy;\n} & Record<string, unknown>;\n\ndeclare module \"@tanstack/react-query\" {\n\tinterface Register {\n\t\tqueryMeta: AppQueryMeta;\n\t}\n}\n\n/** Разрешает сохранять публичную query в долговременном хранилище. */\nexport const persistedQueryMeta = { persist: true } as const satisfies QueryMeta;\n\n/** Помечает query для сброса при изменении серверной сессии. */\nexport const sessionScopedQueryMeta = { sessionScoped: true } as const satisfies QueryMeta;\n\n/**\n * Разрешает bounded persistence только внутри явно установленной server-session identity.\n * Scope и его срок жизни задаются отдельно через `setSessionQueryPersistenceScope`,\n * поэтому query contract не хранит идентификатор пользователя или сессии.\n */\nexport function createSessionQueryPersistenceMeta(policy: SessionQueryPersistencePolicy) {\n\tconst cacheName = policy.cacheName.trim();\n\tif (!/^[a-z0-9][a-z0-9_-]{0,63}$/u.test(cacheName)) {\n\t\tthrow new Error(\"Имя session query cache должно содержать только строчные латинские буквы, цифры, _ и -\");\n\t}\n\tif (!Number.isSafeInteger(policy.maxEntries) || policy.maxEntries < 1 || policy.maxEntries > 5_000) {\n\t\tthrow new Error(\"Лимит session query cache должен быть целым числом от 1 до 5000\");\n\t}\n\tif (policy.refetchOnRestore !== undefined && typeof policy.refetchOnRestore !== \"boolean\") {\n\t\tthrow new Error(\"Политика refetch после восстановления должна быть boolean\");\n\t}\n\tconst monotonicRevisionField = policy.monotonicRevisionField?.trim();\n\tif (monotonicRevisionField !== undefined && !/^[A-Za-z][A-Za-z0-9_]{0,63}$/u.test(monotonicRevisionField)) {\n\t\tthrow new Error(\"Имя поля монотонной ревизии должно быть JavaScript-идентификатором длиной до 64 символов\");\n\t}\n\n\treturn {\n\t\tsessionScoped: true,\n\t\tsessionPersistence: {\n\t\t\tcacheName,\n\t\t\tmaxEntries: policy.maxEntries,\n\t\t\t...(policy.refetchOnRestore === undefined ? {} : { refetchOnRestore: policy.refetchOnRestore }),\n\t\t\t...(monotonicRevisionField === undefined ? {} : { monotonicRevisionField })\n\t\t}\n\t} as const satisfies QueryMeta;\n}\n","import {\n\tDefaultError,\n\tMutation,\n\tMutationCache,\n\tMutationFunctionContext,\n\tQuery,\n\tQueryCache,\n\tQueryClient,\n\tQueryKey\n} from \"@tanstack/react-query\";\n\nimport { createReactQueryPersister } from \"./persistence\";\n\nexport type QueryBroadcastOptions = {\n\tonQueryError?: (error: DefaultError, query: Query<unknown, unknown, unknown>) => void;\n\tonMutationError?: (\n\t\terror: DefaultError,\n\t\tvariables: unknown,\n\t\tonMutateResult: unknown,\n\t\tmutation: Mutation<unknown, unknown, unknown>,\n\t\tcontext: MutationFunctionContext\n\t) => Promise<unknown> | unknown;\n};\n\n/**\n * Создаёт настроенный QueryClient. Singleton lifecycle обеспечивает host-приложение.\n */\nexport function createQueryClient({ onQueryError, onMutationError }: QueryBroadcastOptions) {\n\tconst queryPersister = createReactQueryPersister();\n\n\tconst queryClient = new QueryClient({\n\t\tqueryCache: new QueryCache({ onError: onQueryError }),\n\t\tmutationCache: new MutationCache({ onError: onMutationError }),\n\t\tdefaultOptions: {\n\t\t\t// TODO: Vite env\n\t\t\tqueries: {\n\t\t\t\tstaleTime: 10 * 60 * 1000,\n\t\t\t\tgcTime: 30 * 60 * 1000,\n\t\t\t\tthrowOnError: false,\n\t\t\t\tretry: false,\n\t\t\t\t...(queryPersister ? { persister: queryPersister.persisterFn } : {})\n\t\t\t},\n\t\t\tmutations: {\n\t\t\t\tthrowOnError: false,\n\t\t\t\tretry: false\n\t\t\t}\n\t\t}\n\t});\n\n\treturn queryClient;\n}\n\n/**\n * Контекст, возвращаемый из `onMutateOptimistic`.\n * Содержит снапшот кеша до оптимистичного обновления.\n * Передаётся в `onError`, `onSuccess`, `onSettled` через TanStack Query.\n */\nexport type OptimisticContext<TQueryCacheData> = {\n\tprevious: TQueryCacheData | undefined;\n};\n\n/**\n * _Оптимистичное обновление — это когда мы моментально меняем локальный кэш (и UI) до ответа сервера,\n * чтобы интерфейс был отзывчивым. Если сервер вернул ошибку — откатываем изменения.\n * Если сервер подтвердил — либо принимаем ответ сервера,\n * либо оставляем оптимистичное состояние (в зависимости от ситуации)._\n *\n * Обработчик `onMutate` для оптимистичного обновления.\n *\n * Выполняет три шага:\n * 1. Отменяет активные запросы по ключу — предотвращает гонку с сервером.\n * 2. Сохраняет снапшот текущего состояния кеша.\n * 3. Применяет оптимистичный патч через `patcher`.\n *\n * Возвращает {@link OptimisticContext} — передаётся в `onError` для отката.\n *\n * @example\n * // Обновление точечных данных в списке объектов\n * useMutation({\n * mutationFn: (data: SomeType) => { ... },\n * onMutate: async (data) =>\n * onMutateOptimistic(queryClient, someQueryKey, (old) => {\n * if (!old) return old;\n * return old.map((item) =>\n * item.id === data.id ? { ...item, visible: data.visible } : item\n * );\n * }),\n * onError: (_err, _vars, context) =>\n * onErrorOptimistic(queryClient, someQueryKey, context),\n * onSettled: () =>\n * onSettledOptimistic(queryClient, someQueryKey),\n * })\n */\nexport async function onMutateOptimistic<TQueryCacheData>(\n\tqc: QueryClient,\n\tqueryKey: QueryKey,\n\tpatcher: (oldCache: TQueryCacheData | undefined) => TQueryCacheData | undefined\n): Promise<OptimisticContext<TQueryCacheData>> {\n\t// 1) Отменяем запросы по ключу, чтобы не было гонки с сервером\n\tawait qc.cancelQueries({ queryKey });\n\n\t// 2) Сохраняем снапшот кеша для возможного отката\n\tconst previous = qc.getQueryData<TQueryCacheData>(queryKey);\n\n\t// 3) Применяем оптимистичный патч\n\tqc.setQueryData<TQueryCacheData>(queryKey, patcher);\n\n\treturn { previous };\n}\n\n/**\n * Обработчик `onError` для оптимистичного обновления.\n *\n * Откатывает кеш к снапшоту, сохранённому в {@link OptimisticContext}.\n * Если `context` не определён (например, `onMutate` сам выбросил ошибку) — безопасно ничего не делает.\n *\n * @example\n * onError: (_err, _vars, context) =>\n * onErrorOptimistic(queryClient, someQueryKey, context),\n */\nexport function onErrorOptimistic<TQueryCacheData>(\n\tqc: QueryClient,\n\tqueryKey: QueryKey,\n\tcontext: OptimisticContext<TQueryCacheData> | undefined\n): void {\n\tif (context?.previous !== undefined) {\n\t\tqc.setQueryData<TQueryCacheData>(queryKey, context.previous);\n\t}\n}\n\n/**\n * Обработчик `onSuccess` для оптимистичного обновления — применяет данные сервера в кеш.\n *\n * Используется, когда сервер вернул авторитетные данные, которые нужно слить\n * с текущим состоянием кеша (или заменить его целиком).\n *\n * Если сервер вернул `void` — эту функцию вызывать не нужно:\n * оптимистичное состояние в кеше остаётся без изменений.\n *\n * @param qc - экземпляр `QueryClient`\n * @param queryKey - ключ кеша\n * @param serverData - данные, вернувшиеся от сервера\n * @param merger - функция слияния: получает текущий кеш и ответ сервера, возвращает новое состояние\n *\n * @example\n * // Полная замена кеша ответом сервера\n * onSuccess: (data) =>\n * onSuccessOptimistic(queryClient, someQueryKey, data, (_cache, serverData) => serverData),\n *\n * @example\n * // Точечное обновление одного элемента в списке\n * onSuccess: (updatedItem) =>\n * onSuccessOptimistic(queryClient, someQueryKey, updatedItem,\n * (cache, item) => cache?.map((i) => (i.id === item.id ? item : i))\n * ),\n */\nexport function onSuccessOptimistic<TQueryCacheData, TServerData>(\n\tqc: QueryClient,\n\tqueryKey: QueryKey,\n\tserverData: TServerData,\n\tmerger: (cache: TQueryCacheData | undefined, serverData: TServerData) => TQueryCacheData | undefined\n): void {\n\tqc.setQueryData<TQueryCacheData>(queryKey, (old) => merger(old, serverData));\n}\n\n/**\n * Параметры для {@link onSettledOptimistic}.\n */\nexport type OnSettledOptimisticOptions = {\n\t/**\n\t * Тип рефетча при инвалидации.\n\t * - `\"none\"` (по умолчанию) — помечает кеш устаревшим, но не запускает рефетч немедленно.\n\t * - `\"active\"` — немедленно перезапрашивает активные подписчики.\n\t * - `\"inactive\"` — перезапрашивает неактивные (фоновые) запросы.\n\t * - `\"all\"` — перезапрашивает все подходящие запросы.\n\t */\n\trefetchType?: \"none\" | \"active\" | \"inactive\" | \"all\";\n};\n\n/**\n * Обработчик `onSettled` для оптимистичного обновления.\n *\n * Инвалидирует один или несколько query-ключей после завершения мутации\n * (выполняется и при успехе, и при ошибке).\n *\n * По умолчанию использует `refetchType: \"none\"` — помечает кеш устаревшим,\n * но не запускает немедленный рефетч. Это безопасный вариант при оптимистике:\n * данные в кеше уже актуальны (подтверждены или откатаны), а рефетч произойдёт\n * при следующем монтировании или явном запросе.\n *\n * Broadcast в другие вкладки — ответственность вызывающего кода через `broadcastCacheEvent`.\n *\n * @example\n * onSettled: () =>\n * onSettledOptimistic(queryClient, someQueryKey),\n *\n * @example\n * // С принудительным рефетчем активных подписчиков\n * onSettled: () =>\n * onSettledOptimistic(queryClient, [queryKeyA, queryKeyB], { refetchType: \"active\" }),\n */\nexport async function onSettledOptimistic(\n\tqc: QueryClient,\n\tqueryKeys: QueryKey | QueryKey[],\n\toptions?: OnSettledOptimisticOptions\n): Promise<void> {\n\tconst refetchType = options?.refetchType ?? \"none\";\n\t// Нормализуем одиночный ключ к массиву для единообразной обработки\n\tconst keys: QueryKey[] = isNestedQueryKey(queryKeys) ? queryKeys : [queryKeys];\n\n\tawait Promise.all(keys.map((queryKey) => qc.invalidateQueries({ queryKey, refetchType })));\n}\n\n/**\n * Определяет, является ли значение массивом query-ключей (QueryKey[]),\n * а не одиночным QueryKey.\n *\n * QueryKey в TanStack Query — это `string | readonly unknown[]`.\n * Чтобы отличить `QueryKey[]` от `QueryKey` типа `readonly unknown[]`,\n * проверяем, содержит ли массив хотя бы один вложенный массив или строку на верхнем уровне.\n */\nfunction isNestedQueryKey(value: QueryKey | QueryKey[]): value is QueryKey[] {\n\tif (!Array.isArray(value)) return false;\n\t// Если первый элемент сам является массивом — это QueryKey[]\n\treturn Array.isArray(value[0]);\n}\n","import { type Query, type QueryClient } from \"@tanstack/react-query\";\n\nconst SESSION_SCOPED_QUERY_CACHE_RESET_EVENT = \"session-scoped-query-cache-reset\";\n\ntype SessionScopedQueryCacheResetEvent = {\n\treadonly type: typeof SESSION_SCOPED_QUERY_CACHE_RESET_EVENT;\n};\n\nexport type ResetSessionScopedQueriesOptions = {\n\t/**\n\t * Передавать ли команду сброса другим вкладкам. При обработке входящего\n\t * сообщения значение отключается, чтобы вкладки не создавали цикл событий.\n\t */\n\treadonly broadcast?: boolean;\n};\n\nexport type InstallSessionScopedQueryResetOptions = {\n\t/** Уникальное для приложения имя служебного BroadcastChannel. */\n\treadonly channelName: string;\n};\n\nexport type InstalledSessionScopedQueryReset = {\n\t/** Освобождает канал и прекращает межвкладочную синхронизацию. */\n\treadonly cleanup: () => void;\n};\n\nlet broadcastSessionScopedQueryReset: (() => void) | undefined;\n\n/** Проверяет, относится ли query к данным текущей серверной сессии. */\nexport function isSessionScopedQuery(query: Pick<Query, \"meta\">): boolean {\n\treturn query.meta?.sessionScoped === true;\n}\n\n/**\n * Сбрасывает все session-scoped query после входа, выхода, обновления сессии\n * или потери авторизации.\n *\n * Сначала отменяются незавершённые запросы старой сессии, затем TanStack Query\n * очищает их состояние и повторно запрашивает только активные query. Обычные\n * публичные данные при этом остаются в кеше. По умолчанию отдельная команда\n * сброса отправляется другим вкладкам без передачи содержимого кеша или токенов.\n */\nexport async function resetSessionScopedQueries(queryClient: QueryClient, options: ResetSessionScopedQueriesOptions = {}): Promise<void> {\n\tconst filters = { predicate: isSessionScopedQuery } as const;\n\n\tawait queryClient.cancelQueries(filters);\n\n\tconst matchedQueries = queryClient.getQueryCache().findAll(filters);\n\tfor (const query of matchedQueries) {\n\t\tif (query.getObserversCount() === 0) {\n\t\t\tqueryClient.getQueryCache().remove(query);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Hydrated query хранит SSR-данные как initial state, поэтому одного\n\t\t// query.reset() недостаточно: старая сессия могла бы вернуться в UI.\n\t\tquery.reset();\n\t\tquery.setState({ data: undefined, dataUpdatedAt: 0 });\n\t}\n\n\tawait queryClient.refetchQueries(\n\t\t{\n\t\t\tpredicate: (query) => isSessionScopedQuery(query) && query.getObserversCount() > 0,\n\t\t\ttype: \"active\"\n\t\t},\n\t\t{ cancelRefetch: true }\n\t);\n\n\tif (options.broadcast !== false) {\n\t\tbroadcastSessionScopedQueryReset?.();\n\t}\n}\n\n/**\n * Подключает безопасную межвкладочную синхронизацию смены сессии.\n *\n * Канал передаёт только команду сброса. В отличие от полной синхронизации\n * Query cache, он не публикует данные query и потому подходит для auth-зависимых\n * ответов. На сервере и в браузерах без BroadcastChannel возвращается no-op.\n */\nexport function installSessionScopedQueryReset(\n\tqueryClient: QueryClient,\n\toptions: InstallSessionScopedQueryResetOptions\n): InstalledSessionScopedQueryReset {\n\tif (typeof window === \"undefined\" || typeof BroadcastChannel === \"undefined\") {\n\t\treturn { cleanup: () => undefined };\n\t}\n\n\tconst channel = new BroadcastChannel(options.channelName);\n\tconst broadcast = () => {\n\t\tconst event: SessionScopedQueryCacheResetEvent = {\n\t\t\ttype: SESSION_SCOPED_QUERY_CACHE_RESET_EVENT\n\t\t};\n\t\tchannel.postMessage(event);\n\t};\n\n\tbroadcastSessionScopedQueryReset = broadcast;\n\tchannel.onmessage = (event: MessageEvent<unknown>) => {\n\t\tif (!isSessionScopedQueryCacheResetEvent(event.data)) return;\n\t\tvoid resetSessionScopedQueries(queryClient, { broadcast: false });\n\t};\n\n\treturn {\n\t\tcleanup: () => {\n\t\t\tchannel.onmessage = null;\n\t\t\tchannel.close();\n\t\t\tif (broadcastSessionScopedQueryReset === broadcast) {\n\t\t\t\tbroadcastSessionScopedQueryReset = undefined;\n\t\t\t}\n\t\t}\n\t};\n}\n\n/** Защищает обработчик канала от посторонних или устаревших сообщений. */\nfunction isSessionScopedQueryCacheResetEvent(value: unknown): value is SessionScopedQueryCacheResetEvent {\n\treturn typeof value === \"object\" && value !== null && \"type\" in value && value.type === SESSION_SCOPED_QUERY_CACHE_RESET_EVENT;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../src/query-client/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqC,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAChI,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,aAAa,EAAE,KAAK,oBAAoB,EAAE,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAOjH,eAAO,MAAM,8BAA8B,QAAqC,CAAC;AACjF,eAAO,MAAM,+BAA+B,QAA2B,CAAC;AAExE,KAAK,4BAA4B,GAAG;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,UAAU,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IAC1C,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,4FAA4F;IAC5F,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B,CAAC;AAIF;;;GAGG;AACH,MAAM,MAAM,6BAA6B,CAAC,aAAa,EAAE,OAAO,IAC7D;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACnF;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAE3D,MAAM,MAAM,qBAAqB,CAAC,aAAa,IAAI,YAAY,CAAC,aAAa,CAAC,GAAG;IAChF,oFAAoF;IACpF,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAC5E;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,EAC5B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,KAAK,6BAA6B,CAAC,aAAa,EAAE,OAAO,CAAC,KAC/F,OAAO,CAAC,OAAO,CAAC,CAAC;CACtB,CAAC;AAsDF,uGAAuG;AACvG,wBAAgB,+BAA+B,IAAI,4BAA4B,GAAG,IAAI,CAErF;AAUD,gFAAgF;AAChF,wBAAsB,4BAA4B,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGlF;AAED;;;;GAIG;AACH,wBAAsB,+BAA+B,CAAC,KAAK,EAAE,4BAA4B,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAW/G;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,WAE5D;AAWD,wBAAgB,2BAA2B,CAAC,aAAa,GAAG,OAAO,EAClE,OAAO,GAAE,4BAAiC,GACxC,qBAAqB,CAAC,aAAa,CAAC,GAAG,SAAS,CA4HlD;AAED,wBAAgB,yBAAyB;gBA8DrB,CAAC,EAAE,SAAS,SAAS,QAAQ,WACrC,aAAa,CAAC,CAAC,EAAE,SAAS,CAAC,WAC3B,oBAAoB,CAAC,SAAS,CAAC,SACjC,KAAK,GACV,OAAO,CAAC,CAAC,CAAC;;;+DAlT2B,CAAC;;qFAGzC,CAAF;2BAGoC,CAAC;cAsTpC"}
1
+ {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../src/query-client/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqC,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAChI,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,aAAa,EAAE,KAAK,oBAAoB,EAAE,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAOjH,eAAO,MAAM,8BAA8B,QAAqC,CAAC;AACjF,eAAO,MAAM,+BAA+B,QAA2B,CAAC;AAExE,KAAK,4BAA4B,GAAG;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,UAAU,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IAC1C,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,4FAA4F;IAC5F,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B,CAAC;AAIF;;;GAGG;AACH,MAAM,MAAM,6BAA6B,CAAC,aAAa,EAAE,OAAO,IAC7D;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACnF;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAE3D,MAAM,MAAM,qBAAqB,CAAC,aAAa,IAAI,YAAY,CAAC,aAAa,CAAC,GAAG;IAChF,oFAAoF;IACpF,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAC5E;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,EAC5B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,KAAK,6BAA6B,CAAC,aAAa,EAAE,OAAO,CAAC,KAC/F,OAAO,CAAC,OAAO,CAAC,CAAC;CACtB,CAAC;AAsDF,uGAAuG;AACvG,wBAAgB,+BAA+B,IAAI,4BAA4B,GAAG,IAAI,CAErF;AAUD,gFAAgF;AAChF,wBAAsB,4BAA4B,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGlF;AAED;;;;GAIG;AACH,wBAAsB,+BAA+B,CAAC,KAAK,EAAE,4BAA4B,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAW/G;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,WAE5D;AAkCD,wBAAgB,2BAA2B,CAAC,aAAa,GAAG,OAAO,EAClE,OAAO,GAAE,4BAAiC,GACxC,qBAAqB,CAAC,aAAa,CAAC,GAAG,SAAS,CA4HlD;AAED,wBAAgB,yBAAyB;gBAgFrB,CAAC,EAAE,SAAS,SAAS,QAAQ,WACrC,aAAa,CAAC,CAAC,EAAE,SAAS,CAAC,WAC3B,oBAAoB,CAAC,SAAS,CAAC,SACjC,KAAK,GACV,OAAO,CAAC,CAAC,CAAC;;;+DA3V2B,CAAC;;qFAGzC,CAAF;2BAGoC,CAAC;cAqWpC"}
@@ -3,6 +3,17 @@ export type SessionQueryPersistencePolicy = {
3
3
  readonly cacheName: string;
4
4
  /** Максимальное число последних успешно полученных query в этом кэше. */
5
5
  readonly maxEntries: number;
6
+ /**
7
+ * По умолчанию восстановленный snapshot сразу перепроверяется полным queryFn.
8
+ * `false` допустим только при наличии отдельной лёгкой проверки актуальности.
9
+ */
10
+ readonly refetchOnRestore?: boolean;
11
+ /**
12
+ * Поле верхнего уровня query data с неотрицательной целочисленной ревизией.
13
+ * При наличии поля IndexedDB атомарно отклоняет запоздавшую запись меньшей
14
+ * ревизии, поэтому параллельная вкладка не может откатить сохранённый snapshot.
15
+ */
16
+ readonly monotonicRevisionField?: string;
6
17
  };
7
18
  /**
8
19
  * Общие признаки query, которые управляют инфраструктурой клиентского кеша.
@@ -38,6 +49,8 @@ export declare const sessionScopedQueryMeta: {
38
49
  export declare function createSessionQueryPersistenceMeta(policy: SessionQueryPersistencePolicy): {
39
50
  readonly sessionScoped: true;
40
51
  readonly sessionPersistence: {
52
+ readonly monotonicRevisionField?: string | undefined;
53
+ readonly refetchOnRestore?: boolean | undefined;
41
54
  readonly cacheName: string;
42
55
  readonly maxEntries: number;
43
56
  };
@@ -1 +1 @@
1
- {"version":3,"file":"queryMeta.d.ts","sourceRoot":"","sources":["../../src/query-client/queryMeta.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,6BAA6B,GAAG;IAC3C,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,6BAA6B,CAAC;CAC5D,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE5B,OAAO,QAAQ,uBAAuB,CAAC;IACtC,UAAU,QAAQ;QACjB,SAAS,EAAE,YAAY,CAAC;KACxB;CACD;AAED,sEAAsE;AACtE,eAAO,MAAM,kBAAkB;;CAAiD,CAAC;AAEjF,gEAAgE;AAChE,eAAO,MAAM,sBAAsB;;CAAuD,CAAC;AAE3F;;;;GAIG;AACH,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,6BAA6B;;;;;;EAatF"}
1
+ {"version":3,"file":"queryMeta.d.ts","sourceRoot":"","sources":["../../src/query-client/queryMeta.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,6BAA6B,GAAG;IAC3C,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACzC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,6BAA6B,CAAC;CAC5D,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE5B,OAAO,QAAQ,uBAAuB,CAAC;IACtC,UAAU,QAAQ;QACjB,SAAS,EAAE,YAAY,CAAC;KACxB;CACD;AAED,sEAAsE;AACtE,eAAO,MAAM,kBAAkB;;CAAiD,CAAC;AAEjF,gEAAgE;AAChE,eAAO,MAAM,sBAAsB;;CAAuD,CAAC;AAE3F;;;;GAIG;AACH,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,6BAA6B;;;;;;;;EAyBtF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryuzaki13/react-foundation-lib",
3
- "version": "2.3.1",
3
+ "version": "2.5.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
  # Notifications
6
6
 
7
- Модуль `@ryuzaki13/react-foundation-lib/notifications` хранит очередь коротких уведомлений и предоставляет два способа управления ею:
7
+ Модуль `@ryuzaki13/react-foundation-lib/notifications` хранит активный toast-стек и полную историю уведомлений, а также предоставляет два способа управления ими:
8
8
 
9
9
  - Zustand-store — для React-интерфейса, который отображает уведомления;
10
10
  - объект `notify` — для императивных вызовов из обработчиков, сервисов и `try/catch`.
@@ -88,6 +88,8 @@ export function NotificationsViewport() {
88
88
  }
89
89
  ```
90
90
 
91
+ `items` содержит только активные toast-уведомления и ограничен шестью элементами. Для отдельного центра уведомлений, страницы или содержимого диалога подписывайтесь на `state.history`: эта коллекция не сокращается при TTL, ручном закрытии и переполнении toast-стека.
92
+
91
93
  ### 3. Показывайте уведомления
92
94
 
93
95
  ```ts
@@ -137,17 +139,21 @@ type Notification = {
137
139
  ```ts
138
140
  type NotificationsStore = {
139
141
  items: Notification[];
142
+ history: Notification[];
140
143
  actions: {
141
144
  push(input): NotificationId;
142
145
  update(id, patch): boolean;
143
146
  upsert(inputWithId): NotificationId;
144
147
  dismiss(id): void;
145
148
  clear(): void;
149
+ clearHistory(): void;
146
150
  };
147
151
  };
148
152
  ```
149
153
 
150
- Очередь ограничена шестью элементами. Новое уведомление помещается в начало. При добавлении с уже существующим `id` старый элемент удаляется, а новый получает новое `createdAt`.
154
+ `items` — стек видимых toast-уведомлений, ограниченный шестью элементами. `history` — полная история за время жизни store. Новое уведомление помещается в начало обеих коллекций. При добавлении с уже существующим `id` старый элемент заменяется, а новый получает новое `createdAt`.
155
+
156
+ История живёт в памяти и не является журналом аудита: она сбрасывается при пересоздании store или явном `clearHistory()`. Поскольку `actions` уведомлений содержат функции, модуль намеренно не сохраняет историю в `localStorage` и не сериализует её.
151
157
 
152
158
  ### `push(input)`
153
159
 
@@ -196,9 +202,11 @@ notificationsStore.getState().actions.upsert({
196
202
 
197
203
  Важная деталь: при обновлении существующего элемента `upsert` меняет `type`, `title`, `message`, `ttlMs` и `dismissible`, но не заменяет `actions`. Для явной смены действий используйте `update(id, { actions })`.
198
204
 
199
- ### `dismiss(id)` и `clear()`
205
+ ### `dismiss(id)`, `clear()` и `clearHistory()`
206
+
207
+ `dismiss(id)` скрывает один toast и очищает его таймер. `clear()` скрывает весь toast-стек и очищает все таймеры. Обе операции сохраняют записи в `history`.
200
208
 
201
- `dismiss(id)` удаляет один элемент и очищает его таймер. `clear()` удаляет всю очередь и все таймеры.
209
+ `clearHistory()` явно очищает историю, не закрывая активные toast-уведомления. Чтобы полностью сбросить обе коллекции, последовательно вызовите `clear()` и `clearHistory()`.
202
210
 
203
211
  ## Императивный API `notify`
204
212
 
@@ -223,6 +231,7 @@ notify.upsert(inputWithId);
223
231
  notify.update(id, patch);
224
232
  notify.dismiss(id);
225
233
  notify.clear();
234
+ notify.clearHistory();
226
235
 
227
236
  notify.success(message, options);
228
237
  notify.info(message, options);
@@ -258,7 +267,7 @@ async function importFile(file: File) {
258
267
  - `update(message)` — меняет текст, не перезапуская таймер;
259
268
  - `success(message, ttlMs = 2500)` — делает уведомление успешным и закрываемым;
260
269
  - `error(message, ttlMs = 5000)` — делает уведомление ошибкой и закрываемым;
261
- - `dismiss()` — удаляет уведомление.
270
+ - `dismiss()` — скрывает toast, сохраняя уведомление в истории.
262
271
 
263
272
  При переданном стабильном `id` повторный `progress` продолжит существующий элемент. Это удобно для операции, которая может стартовать из нескольких мест.
264
273
 
@@ -307,17 +316,19 @@ function App() {
307
316
 
308
317
  ### Использование уведомления вместо состояния операции
309
318
 
310
- Очередь ограничена шестью элементами и живёт в памяти страницы. Не используйте её как журнал аудита, источник истины о фоновой задаче или долговременное хранилище ошибок.
319
+ История живёт в памяти страницы. Не используйте её как журнал аудита, источник истины о фоновой задаче или долговременное хранилище ошибок.
311
320
 
312
321
  ## Краткая карта API
313
322
 
314
323
  | Экспорт | Назначение |
315
324
  | ---------------------------- | -------------------------------------------------------- |
316
- | `createNotificationsStore()` | Создать независимый Zustand-store уведомлений |
317
- | `bindNotifications(store)` | Привязать store к глобальному фасаду `notify` |
318
- | `notify` | Императивно добавлять, обновлять и закрывать уведомления |
319
- | `Notification` | Полная модель сохранённого элемента |
320
- | `NotificationPushInput` | Параметры добавления элемента |
321
- | `NotificationUpdatePatch` | Допустимые поля частичного обновления |
322
- | `NotificationsStoreApi` | Тип vanilla Zustand-store |
323
- | `NotificationAction` | Кнопка-действие уведомления |
325
+ | `createNotificationsStore()` | Создать независимый Zustand-store toast-стека и истории |
326
+ | `bindNotifications(store)` | Привязать store к глобальному фасаду `notify` |
327
+ | `notify` | Императивно добавлять, обновлять и закрывать уведомления |
328
+ | `Notification` | Полная модель сохранённого элемента |
329
+ | `NotificationPushInput` | Параметры добавления элемента |
330
+ | `NotificationUpdatePatch` | Допустимые поля частичного обновления |
331
+ | `NotificationsState` | Состояние с активными `items` и полной `history` |
332
+ | `NotificationsActions` | Действия toast-стека и явная очистка истории |
333
+ | `NotificationsStoreApi` | Тип vanilla Zustand-store |
334
+ | `NotificationAction` | Кнопка-действие уведомления |
@@ -541,10 +541,7 @@ queryOptions({
541
541
  Только для явно согласованного offline allowlist создайте bounded metadata:
542
542
 
543
543
  ```ts
544
- import {
545
- createSessionQueryPersistenceMeta,
546
- setSessionQueryPersistenceScope
547
- } from "@ryuzaki13/react-foundation-lib/query-client";
544
+ import { createSessionQueryPersistenceMeta, setSessionQueryPersistenceScope } from "@ryuzaki13/react-foundation-lib/query-client";
548
545
 
549
546
  await setSessionQueryPersistenceScope({
550
547
  id: session.id,
@@ -556,7 +553,8 @@ queryOptions({
556
553
  queryFn: fetchWeek,
557
554
  meta: createSessionQueryPersistenceMeta({
558
555
  cacheName: "opened-weeks",
559
- maxEntries: 24
556
+ maxEntries: 24,
557
+ refetchOnRestore: true
560
558
  }),
561
559
  networkMode: "offlineFirst"
562
560
  });
@@ -566,6 +564,25 @@ Scope устанавливается отдельно от query contract, не
566
564
  При `null`, истёкшем `expiresAt` или смене `id` старые session snapshots удаляются. `networkMode: "offlineFirst"` нужен, чтобы
567
565
  TanStack вызвал persister до network pause. Cached data остаётся неавторитетным: write после reconnect обязан повторно получить серверный state.
568
566
 
567
+ Если consumer имеет отдельный лёгкий checker актуальности, полный запрос после
568
+ restore можно отключить. Для revisioned snapshot передайте поле верхнего уровня
569
+ с decimal string revision:
570
+
571
+ ```ts
572
+ meta: createSessionQueryPersistenceMeta({
573
+ cacheName: "reference-data",
574
+ maxEntries: 16,
575
+ refetchOnRestore: false,
576
+ monotonicRevisionField: "revision"
577
+ });
578
+ ```
579
+
580
+ `refetchOnRestore: false` без внешнего checker оставляет consumer ответственным
581
+ за бесконечно устаревшие данные. `monotonicRevisionField` не создаёт новые
582
+ storage keys: запись того же query key обновляется атомарно, а меньшая decimal
583
+ revision из запоздавшей вкладки не заменяет уже сохранённую большую. Если поле
584
+ отсутствует или не является строкой из цифр, запись использует обычную policy.
585
+
569
586
  ### Meta нельзя совмещать
570
587
 
571
588
  Неверно:
@@ -824,7 +841,7 @@ const persister = createReactQueryPersister();
824
841
  prefix: systemIdentifier,
825
842
  buster: adapterBuster ?? buildTimeBuster,
826
843
  maxAge: 90 * 24 * 60 * 60 * 1000,
827
- refetchOnRestore: true,
844
+ refetchOnRestore: true, // либо session policy конкретного cache
828
845
  filters: { predicate: shouldPersistQuery }
829
846
  }
830
847
  ```
@@ -894,7 +911,8 @@ deserialize: (query) => query;
894
911
  3. По `prefix-queryHash` читается IndexedDB.
895
912
  4. Проверяются buster и возраст записи.
896
913
  5. Подходящее state восстанавливается в Query cache.
897
- 6. Если восстановленная query stale, `refetchOnRestore: true` запускает обновление.
914
+ 6. Если восстановленная query stale, effective `refetchOnRestore` запускает
915
+ обновление; session cache может отключить его при наличии внешнего checker.
898
916
  7. Если записи нет/она протухла/не совпал buster, выполняется обычный `queryFn`.
899
917
  8. Успешный результат планируется к записи в IndexedDB.
900
918
 
@@ -2389,12 +2407,12 @@ Profile tests подтверждают:
2389
2407
 
2390
2408
  ### Query metadata
2391
2409
 
2392
- | Symbol | Вид | Назначение |
2393
- | ------------------------ | ----- | ----------------------------------------------- |
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. |
2410
+ | Symbol | Вид | Назначение |
2411
+ | ----------------------------------- | -------- | -------------------------------------------------------- |
2412
+ | `AppQueryMeta` | type | Public/session persistence и consumer metadata. |
2413
+ | `SessionQueryPersistencePolicy` | type | Имя и bounded limit session cache. |
2414
+ | `persistedQueryMeta` | const | Opt-in public persistence marker. |
2415
+ | `sessionScopedQueryMeta` | const | Session reset marker без browser persistence. |
2398
2416
  | `createSessionQueryPersistenceMeta` | function | Явный opt-in bounded persistence текущей server session. |
2399
2417
 
2400
2418
  ### Persistence project adapter