@ryuzaki13/react-foundation-lib 1.6.0 → 1.8.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
@@ -655,6 +655,7 @@ Service worker runtime helpers живут в [`pwa/serviceWorker.ts`](./src/pwa/
655
655
  | `persistedQueryMeta`, `shouldPersistQuery`, `createReactQueryPersister`, `createIndexedDbQueryStorage` | Opt-in persistence справочников в IndexedDB. |
656
656
  | `configureQueryPersistenceProjectAdapter`, `getQueryPersistenceProjectAdapter` | Host namespace и buster для persistent cache. |
657
657
  | `installReactQueryBroadcast`, `broadcastCacheEvent`, `setBroadcastFn` | Sync query cache и cache events между вкладками. |
658
+ | `installQueryInvalidationBroadcast` | Scoped invalidation между вкладками без передачи Query data. |
658
659
  | `sessionScopedQueryMeta`, `resetSessionScopedQueries`, `installSessionScopedQueryReset` | Очистка auth/session-dependent query локально и между вкладками. |
659
660
  | `onMutateOptimistic`, `onErrorOptimistic`, `onSuccessOptimistic`, `onSettledOptimistic` | Helpers для точечного optimistic update. |
660
661
 
@@ -1,3 +1,4 @@
1
1
  export * from "./excel";
2
+ export * from "./matrixWorkbook";
2
3
  export * from "./tableExport";
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/excel/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/excel/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC"}
@@ -1,19 +1,21 @@
1
- import { Sn as e, c as t, l as n, pn as r, s as i, u as a, xn as o } from "../chunks/odata-service-CWkHASdw.js";
2
- import { d as s, t as c } from "../chunks/validate-DyzojM0w.js";
3
- import l from "write-excel-file/browser";
1
+ import { downloadFileFromBlob as e } from "../dom/index.js";
2
+ import { Sn as t, c as n, l as r, pn as i, s as a, u as o, xn as s } from "../chunks/odata-service-CWkHASdw.js";
3
+ import { d as c, t as l } from "../chunks/validate-DyzojM0w.js";
4
+ import { escapeXmlValue as u } from "../xml/index.js";
5
+ import d from "write-excel-file/browser";
4
6
  //#region src/excel/excel.ts
5
- var u = {
7
+ var f = {
6
8
  information: "#2563EB",
7
9
  success: "#107C10",
8
10
  warning: "#B7791F",
9
11
  error: "#C50F1F"
10
- }, d = {
12
+ }, p = {
11
13
  fontWeight: "bold",
12
14
  bottomBorderStyle: "thin",
13
15
  bottomBorderColor: "#D9D9D9"
14
- }, f = 31, p = /[\u0000-\u001f[\]/\\:*?]+/g, m = /^'+|'+$/g, h = /\s+/g;
15
- function g(e) {
16
- let t = e ? u[e] : void 0;
16
+ }, m = 31, h = /[\u0000-\u001f[\]/\\:*?]+/g, g = /^'+|'+$/g, ee = /\s+/g;
17
+ function te(e) {
18
+ let t = e ? f[e] : void 0;
17
19
  return t ? { textColor: t } : void 0;
18
20
  }
19
21
  function _(e) {
@@ -36,10 +38,10 @@ function y(e) {
36
38
  function b(e) {
37
39
  let t = /* @__PURE__ */ new Set(), n = [];
38
40
  for (let r = 0; r < e.length; r += 1) {
39
- let i = `Лист ${r + 1}`, a = e[r]?.replace(p, " ").replace(h, " ").trim().replace(m, "").trim() || i, o = a.slice(0, f), s = 2;
41
+ let i = `Лист ${r + 1}`, a = e[r]?.replace(h, " ").replace(ee, " ").trim().replace(g, "").trim() || i, o = a.slice(0, m), s = 2;
40
42
  for (; t.has(o.toLocaleLowerCase());) {
41
43
  let e = ` (${s})`;
42
- o = `${a.slice(0, f - e.length)}${e}`, s += 1;
44
+ o = `${a.slice(0, m - e.length)}${e}`, s += 1;
43
45
  }
44
46
  t.add(o.toLocaleLowerCase()), n.push(o);
45
47
  }
@@ -51,14 +53,14 @@ function x(e, t) {
51
53
  let t = typeof e == "number" ? e : Number(e);
52
54
  return Number.isFinite(t) ? t : void 0;
53
55
  }
54
- return t === String ? String(e) : t === Boolean ? !!e : t === Date ? e instanceof Date ? e : r(e) ?? void 0 : e;
56
+ return t === String ? String(e) : t === Boolean ? !!e : t === Date ? e instanceof Date ? e : i(e) ?? void 0 : e;
55
57
  }
56
58
  }
57
59
  function S(e) {
58
60
  let t = [e.columns.map((e) => ({
59
61
  type: String,
60
62
  value: e.header,
61
- ...d
63
+ ...p
62
64
  }))];
63
65
  for (let n = 0; n < e.rows.length; n++) {
64
66
  let r = e.rows[n], i = [];
@@ -90,7 +92,7 @@ async function C(e) {
90
92
  columns: e.columns.map((e) => ({ width: e.width }))
91
93
  };
92
94
  }), i = y(n);
93
- await l(r, { features: i ? [i] : void 0 }).toFile(e.fileName);
95
+ await d(r, { features: i ? [i] : void 0 }).toFile(e.fileName);
94
96
  }
95
97
  async function w(e) {
96
98
  await C({
@@ -105,63 +107,177 @@ async function w(e) {
105
107
  });
106
108
  }
107
109
  //#endregion
110
+ //#region src/excel/matrixWorkbook.ts
111
+ var T = {
112
+ left: .2,
113
+ right: .2,
114
+ top: .25,
115
+ bottom: .25,
116
+ header: .1,
117
+ footer: .1
118
+ }, ne = {
119
+ a4: 9,
120
+ a3: 8
121
+ };
122
+ function E(e) {
123
+ return u(e.replaceAll("&", "&&"));
124
+ }
125
+ function D(e) {
126
+ let t = e, n = "";
127
+ for (; t > 0;) {
128
+ let e = (t - 1) % 26;
129
+ n = String.fromCharCode(65 + e) + n, t = Math.floor((t - 1) / 26);
130
+ }
131
+ return n;
132
+ }
133
+ function O(e, t) {
134
+ if (!Number.isInteger(e) || e <= 0) throw Error(`${t} должен быть положительным целым числом`);
135
+ }
136
+ function k(e, t) {
137
+ if (O(e[0], `${t}: начало`), O(e[1], `${t}: конец`), e[0] > e[1]) throw Error(`${t}: начало не может быть больше конца`);
138
+ }
139
+ function A(e) {
140
+ e.fitToWidthPages !== void 0 && O(e.fitToWidthPages, "fitToWidthPages"), e.fitToHeightPages !== void 0 && O(e.fitToHeightPages, "fitToHeightPages"), e.repeatRows && k(e.repeatRows, "repeatRows"), e.repeatColumns && k(e.repeatColumns, "repeatColumns");
141
+ for (let t of e.columnBreaksAfter ?? []) O(t, "columnBreaksAfter");
142
+ for (let t of e.rowBreaksAfter ?? []) O(t, "rowBreaksAfter");
143
+ if (e.printArea && (O(e.printArea.firstRow, "printArea.firstRow"), O(e.printArea.lastRow, "printArea.lastRow"), O(e.printArea.firstColumn, "printArea.firstColumn"), O(e.printArea.lastColumn, "printArea.lastColumn"), e.printArea.firstRow > e.printArea.lastRow || e.printArea.firstColumn > e.printArea.lastColumn)) throw Error("printArea должен задавать непустой прямоугольник");
144
+ }
145
+ function j(e, t, n) {
146
+ if (!t && !n) return "";
147
+ let r = (e) => e ? [
148
+ e.left ? `&amp;L${E(e.left)}` : "",
149
+ e.center ? `&amp;C${E(e.center)}` : "",
150
+ e.right ? `&amp;R${E(e.right)}` : "",
151
+ e.includePageNumber ? "&amp;RСтраница &amp;P из &amp;N" : ""
152
+ ].join("") : "", i = r(t), a = r(n);
153
+ return `<${e}>${i ? `<oddHeader>${i}</oddHeader>` : ""}${a ? `<oddFooter>${a}</oddFooter>` : ""}</${e}>`;
154
+ }
155
+ function M(e, t) {
156
+ if (!t || t.length === 0) return "";
157
+ let n = [...new Set(t)].sort((e, t) => e - t);
158
+ return `<${e} count="${n.length}" manualBreakCount="${n.length}">${n.map((e) => `<brk id="${e}" min="0" max="16383" man="1"/>`).join("")}</${e}>`;
159
+ }
160
+ function N(e, t) {
161
+ let n = t.margins ?? T, r = t.fitToWidthPages !== void 0 || t.fitToHeightPages !== void 0 ? "<sheetPr><pageSetUpPr fitToPage=\"1\" autoPageBreaks=\"0\"/></sheetPr>" : "", i = r ? e.replace("<sheetViews", `${r}<sheetViews`) : e, a = [
162
+ `<pageMargins left="${n.left}" right="${n.right}" top="${n.top}" bottom="${n.bottom}" header="${n.header}" footer="${n.footer}"/>`,
163
+ `<pageSetup ${[
164
+ `paperSize="${ne[t.paperSize ?? "a4"]}"`,
165
+ `orientation="${t.orientation}"`,
166
+ t.fitToWidthPages === void 0 ? "" : `fitToWidth="${t.fitToWidthPages}"`,
167
+ t.fitToHeightPages === void 0 ? "" : `fitToHeight="${t.fitToHeightPages}"`,
168
+ t.pageOrder ? `pageOrder="${t.pageOrder}"` : "",
169
+ "usePrinterDefaults=\"0\""
170
+ ].filter(Boolean).join(" ")}/>`,
171
+ j("headerFooter", t.header, t.footer),
172
+ M("rowBreaks", t.rowBreaksAfter),
173
+ M("colBreaks", t.columnBreaksAfter)
174
+ ].join(""), o = i.indexOf("<drawing");
175
+ return o >= 0 ? `${i.slice(0, o)}${a}${i.slice(o)}` : i.replace("</worksheet>", `${a}</worksheet>`);
176
+ }
177
+ function P(e) {
178
+ return `'${e.replaceAll("'", "''")}'`;
179
+ }
180
+ function F(e, t) {
181
+ let n = [];
182
+ for (let r = 0; r < t.length; r += 1) {
183
+ let i = t[r]?.print;
184
+ if (!i) continue;
185
+ let a = P(e[r] ?? `Лист ${r + 1}`), o = [i.repeatRows ? `${a}!$${i.repeatRows[0]}:$${i.repeatRows[1]}` : "", i.repeatColumns ? `${a}!$${D(i.repeatColumns[0])}:$${D(i.repeatColumns[1])}` : ""].filter(Boolean).join(",");
186
+ if (o && n.push(`<definedName name="_xlnm.Print_Titles" localSheetId="${r}">${u(o)}</definedName>`), i.printArea) {
187
+ let { firstRow: e, lastRow: t, firstColumn: o, lastColumn: s } = i.printArea, c = `${a}!$${D(o)}$${e}:$${D(s)}$${t}`;
188
+ n.push(`<definedName name="_xlnm.Print_Area" localSheetId="${r}">${u(c)}</definedName>`);
189
+ }
190
+ }
191
+ return n.join("");
192
+ }
193
+ function I(e, t) {
194
+ if (!t.some(({ print: e }) => e)) return;
195
+ let n = F(e, t);
196
+ return { files: { transform: {
197
+ "xl/worksheets/sheet{id}.xml": { transform: (e, n, r) => {
198
+ let i = t[r.sheetIndex]?.print;
199
+ return i ? N(e, i) : e;
200
+ } },
201
+ "xl/workbook.xml": { transform: (e) => n ? e.replace("</sheets>", `</sheets><definedNames>${n}</definedNames>`) : e }
202
+ } } };
203
+ }
204
+ async function L(e) {
205
+ if (e.sheets.length === 0) throw Error("Для формирования Excel-книги нужен минимум один лист");
206
+ for (let t of e.sheets) {
207
+ if (t.rows.length === 0) throw Error(`Лист «${t.name}» не содержит строк`);
208
+ t.print && A(t.print);
209
+ }
210
+ let t = b(e.sheets.map(({ name: e }) => e)), n = e.sheets.map((e, n) => ({
211
+ data: e.rows.map((e) => [...e]),
212
+ sheet: t[n],
213
+ columns: e.columns?.map((e) => ({ width: e.width })),
214
+ showGridLines: e.showGridLines,
215
+ zoomScale: e.zoomScale
216
+ })), r = I(t, e.sheets);
217
+ return d(n, { features: r ? [r] : void 0 }).toBlob();
218
+ }
219
+ async function R(t, n) {
220
+ let r = await L(n);
221
+ e(t, r);
222
+ }
223
+ //#endregion
108
224
  //#region src/excel/tableExport.ts
109
- var T = "#,##0", E = "#,##0.0", D = "dd.mm.yyyy", O = "dd.mm", k = "d mmm", A = "d mmmm", j = "mmm yyyy", M = "mmmm yyyy", N = "hh:mm", P = "hh:mm:ss", F = `${D} ${N}`, I = `${D} ${P}`;
110
- function L(e) {
111
- let t = c(e).plan?.steps.find((e) => e.type === "typedValueFormat");
225
+ var z = "#,##0", B = "#,##0.0", V = "dd.mm.yyyy", H = "dd.mm", U = "d mmm", re = "d mmmm", W = "mmm yyyy", G = "mmmm yyyy", K = "hh:mm", q = "hh:mm:ss", J = `${V} ${K}`, Y = `${V} ${q}`;
226
+ function X(e) {
227
+ let t = l(e).plan?.steps.find((e) => e.type === "typedValueFormat");
112
228
  return t?.type === "typedValueFormat" ? t.config : void 0;
113
229
  }
114
- function R(e) {
115
- return e.clientOnly ? Number : t(e.odataType) ? Date : i(e.odataType) ? Boolean : a(e.odataType) || e.role === "measure" ? Number : String;
230
+ function Z(e) {
231
+ return e.clientOnly ? Number : n(e.odataType) ? Date : a(e.odataType) ? Boolean : o(e.odataType) || e.role === "measure" ? Number : String;
116
232
  }
117
- function z(e) {
118
- let t = L(e.formattersPipeline)?.numberPresetName?.trim();
119
- return t && s(t) ? t : n(e.odataType) ? "integer" : "decimal";
233
+ function ie(e) {
234
+ let t = X(e.formattersPipeline)?.numberPresetName?.trim();
235
+ return t && c(t) ? t : r(e.odataType) ? "integer" : "decimal";
120
236
  }
121
- function B(e) {
122
- let t = s(z(e));
123
- return t ? t.decimals > 0 ? `#,##0.${"0".repeat(t.decimals)}` : T : E;
237
+ function ae(e) {
238
+ let t = c(ie(e));
239
+ return t ? t.decimals > 0 ? `#,##0.${"0".repeat(t.decimals)}` : z : B;
124
240
  }
125
- function V(e) {
126
- return L(e.formattersPipeline)?.datePresetName?.trim() || (e.odataType === "time" ? o.time : e.odataType === "datetime" || e.odataType === "datetimeOffset" ? o.datetime : o.date);
241
+ function oe(e) {
242
+ return X(e.formattersPipeline)?.datePresetName?.trim() || (e.odataType === "time" ? s.time : e.odataType === "datetime" || e.odataType === "datetimeOffset" ? s.datetime : s.date);
127
243
  }
128
- function H(e) {
244
+ function Q(e) {
129
245
  return e.replace(/H/g, "h").replace(/M/g, "m");
130
246
  }
131
- function U(e) {
132
- if (e === o.odataDatetime || e === o.abapDatetime) return I;
133
- if (e === o.odataDate || e === o.abapDate) return D;
134
- if (e === o.monthShort) return O;
135
- if (e === o.monthMedium) return k;
136
- if (e === o.monthLong) return A;
137
- if (e === o.monthYearMedium) return j;
138
- if (e === o.monthYearLong) return M;
139
- if (e.startsWith("datetime")) return e === o.datetime || e === o.datetimeShort ? F : I;
140
- if (e.startsWith("time")) return e === o.time || e === o.timeShort ? N : P;
141
- if (e.startsWith("date")) return D;
142
- }
143
- function W(e, t) {
144
- return U(e) || (t?.pattern ? H(t.pattern) : H(e));
145
- }
146
- function G(t) {
147
- let n = V(t);
148
- return W(n, e(n));
149
- }
150
- function K(e, t) {
151
- if (t === Number) return B(e);
152
- if (t === Date) return G(e);
153
- }
154
- function q(e) {
155
- let t = R(e);
247
+ function $(e) {
248
+ if (e === s.odataDatetime || e === s.abapDatetime) return Y;
249
+ if (e === s.odataDate || e === s.abapDate) return V;
250
+ if (e === s.monthShort) return H;
251
+ if (e === s.monthMedium) return U;
252
+ if (e === s.monthLong) return re;
253
+ if (e === s.monthYearMedium) return W;
254
+ if (e === s.monthYearLong) return G;
255
+ if (e.startsWith("datetime")) return e === s.datetime || e === s.datetimeShort ? J : Y;
256
+ if (e.startsWith("time")) return e === s.time || e === s.timeShort ? K : q;
257
+ if (e.startsWith("date")) return V;
258
+ }
259
+ function se(e, t) {
260
+ return $(e) || (t?.pattern ? Q(t.pattern) : Q(e));
261
+ }
262
+ function ce(e) {
263
+ let n = oe(e);
264
+ return se(n, t(n));
265
+ }
266
+ function le(e, t) {
267
+ if (t === Number) return ae(e);
268
+ if (t === Date) return ce(e);
269
+ }
270
+ function ue(e) {
271
+ let t = Z(e);
156
272
  return {
157
273
  id: e.id,
158
274
  header: e.header,
159
275
  width: e.width,
160
276
  type: t,
161
- format: K(e, t)
277
+ format: le(e, t)
162
278
  };
163
279
  }
164
- async function J({ fileName: e, table: t, autoFilter: n = !0 }) {
280
+ async function de({ fileName: e, table: t, autoFilter: n = !0 }) {
165
281
  await w({
166
282
  fileName: e,
167
283
  columns: t.columns,
@@ -170,7 +286,7 @@ async function J({ fileName: e, table: t, autoFilter: n = !0 }) {
170
286
  getCellStyle: ({ rowIndex: e, column: n }) => t.cellStyles?.[e]?.[n.id]
171
287
  });
172
288
  }
173
- async function Y({ fileName: e, sheets: t }) {
289
+ async function fe({ fileName: e, sheets: t }) {
174
290
  await C({
175
291
  fileName: e,
176
292
  sheets: t.map(({ name: e, table: t, autoFilter: n = !0 }) => ({
@@ -183,6 +299,6 @@ async function Y({ fileName: e, sheets: t }) {
183
299
  });
184
300
  }
185
301
  //#endregion
186
- export { v as buildExcelAutoFilterRef, S as buildExcelSheetData, q as createTableExcelColumn, w as downloadExcelFile, C as downloadExcelWorkbook, J as downloadResolvedExcelTable, Y as downloadResolvedExcelWorkbook, g as resolveExcelCellStyleFromState, b as resolveExcelSheetNames, R as resolveTableExcelColumnType };
302
+ export { v as buildExcelAutoFilterRef, S as buildExcelSheetData, L as createExcelMatrixWorkbookBlob, ue as createTableExcelColumn, w as downloadExcelFile, R as downloadExcelMatrixWorkbook, C as downloadExcelWorkbook, de as downloadResolvedExcelTable, fe as downloadResolvedExcelWorkbook, te as resolveExcelCellStyleFromState, b as resolveExcelSheetNames, Z as resolveTableExcelColumnType };
187
303
 
188
304
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/excel/excel.ts","../../src/excel/tableExport.ts"],"sourcesContent":["import writeXlsxFile, { type Cell, type CellObject, type Feature, type Sheet, type SheetData } from \"write-excel-file/browser\";\n\nimport { parseDate } from \"../formatters/date\";\nimport { type State } from \"../types\";\n\nexport type ExcelColumn = {\n\tid: string;\n\theader: string;\n\twidth?: number;\n\ttype?: StringConstructor | NumberConstructor | DateConstructor | BooleanConstructor;\n\tformat?: string;\n};\n\nexport type ExcelRow = Record<string, unknown>;\n\nexport type ExcelCellStyle = Omit<CellObject, \"value\" | \"type\" | \"format\">;\n\nexport type DownloadExcelFileArgs = {\n\tfileName: string;\n\tcolumns: ExcelColumn[];\n\trows: ExcelRow[];\n\tautoFilter?: boolean;\n\tgetCellStyle?: (args: { row: ExcelRow; column: ExcelColumn; rowIndex: number; columnIndex: number }) => ExcelCellStyle | undefined;\n};\n\n/** Описание одного табличного листа внутри Excel-книги. */\nexport type ExcelWorkbookSheet = Omit<DownloadExcelFileArgs, \"fileName\"> & {\n\t/** Желаемое имя листа; writer нормализует ограничения формата и конфликты имён. */\n\tname: string;\n};\n\n/** Аргументы формирования Excel-книги с одним или несколькими табличными листами. */\nexport type DownloadExcelWorkbookArgs = {\n\t/** Имя скачиваемого `.xlsx` файла. */\n\tfileName: string;\n\t/** Листы книги в порядке отображения в Excel. */\n\tsheets: ExcelWorkbookSheet[];\n};\n\ntype BrowserExcelFileContent = File | Blob | ArrayBuffer;\n\nconst VALUE_STATE_TEXT_COLORS: Partial<Record<State, string>> = {\n\tinformation: \"#2563EB\",\n\tsuccess: \"#107C10\",\n\twarning: \"#B7791F\",\n\terror: \"#C50F1F\"\n};\n\nconst HEADER_CELL_STYLE = {\n\tfontWeight: \"bold\",\n\tbottomBorderStyle: \"thin\",\n\tbottomBorderColor: \"#D9D9D9\"\n} satisfies ExcelCellStyle;\n\nconst EXCEL_SHEET_NAME_MAX_LENGTH = 31;\nconst EXCEL_SHEET_NAME_INVALID_CHARACTERS = /[\\u0000-\\u001f[\\]/\\\\:*?]+/g;\nconst EXCEL_SHEET_NAME_EDGE_APOSTROPHES = /^'+|'+$/g;\nconst EXCEL_SHEET_NAME_WHITESPACE = /\\s+/g;\n\n/**\n * Приводит общий value-state проекта к базовому Excel-стилю.\n *\n * Helper живет в shared Excel-слое, чтобы analytical/detail экспорт не\n * дублировали палитру статусов и одинаково подсвечивали warning/error/success.\n */\nexport function resolveExcelCellStyleFromState(state: State | undefined): ExcelCellStyle | undefined {\n\tconst textColor = state ? VALUE_STATE_TEXT_COLORS[state] : undefined;\n\treturn textColor ? { textColor } : undefined;\n}\n\n/**\n * Переводит zero-based индекс колонки в Excel-адрес (`0 -> A`, `27 -> AB`).\n * Нужен для XML-диапазонов, потому что `write-excel-file` не предоставляет\n * отдельного API для построения ссылок вида `A1:C10`.\n */\nfunction numberToColumnName(index: number): string {\n\tlet value = index + 1;\n\tlet out = \"\";\n\n\twhile (value > 0) {\n\t\tconst remainder = (value - 1) % 26;\n\t\tout = String.fromCharCode(65 + remainder) + out;\n\t\tvalue = Math.floor((value - 1) / 26);\n\t}\n\n\treturn out;\n}\n\n/**\n * Строит диапазон автофильтра для всей выгруженной таблицы.\n *\n * Возвращает `undefined` для пустой сетки, чтобы не вставлять в xlsx\n * невалидный `<autoFilter>` без реального диапазона.\n */\nexport function buildExcelAutoFilterRef(columnCount: number, rowCount: number): string | undefined {\n\tif (columnCount <= 0 || rowCount <= 0) return undefined;\n\n\treturn `A1:${numberToColumnName(columnCount - 1)}${rowCount}`;\n}\n\n/**\n * Добавляет autoFilter через feature-transform `write-excel-file`.\n *\n * Библиотека умеет трансформировать XML листа, но не имеет высокоуровневого\n * параметра `autoFilter`, поэтому здесь изолирован низкоуровневый OpenXML-хук.\n */\nfunction createAutoFilterFeature<FileContent>(refs: readonly (string | undefined)[]): Feature<FileContent> | undefined {\n\tif (!refs.some(Boolean)) return undefined;\n\n\treturn {\n\t\tfiles: {\n\t\t\ttransform: {\n\t\t\t\t\"xl/worksheets/sheet{id}.xml\": {\n\t\t\t\t\ttransform: (content, _options, properties) => {\n\t\t\t\t\t\tconst ref = refs[properties.sheetIndex];\n\t\t\t\t\t\treturn ref ? content.replace(\"</sheetData>\", `</sheetData><autoFilter ref=\"${ref}\"/>`) : content;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\n/**\n * Нормализует имена листов по ограничениям Excel и делает их уникальными.\n *\n * Runtime-заголовки могут содержать запрещённые символы, превышать 31 знак или\n * совпадать между несколькими представлениями одной конфигурации. Нормализация\n * выполняется централизованно, чтобы все consumers получали открываемую книгу.\n */\nexport function resolveExcelSheetNames(names: readonly string[]): string[] {\n\tconst usedNames = new Set<string>();\n\tconst resolvedNames: string[] = [];\n\n\tfor (let index = 0; index < names.length; index += 1) {\n\t\tconst fallbackName = `Лист ${index + 1}`;\n\t\tconst normalizedName = names[index]\n\t\t\t?.replace(EXCEL_SHEET_NAME_INVALID_CHARACTERS, \" \")\n\t\t\t.replace(EXCEL_SHEET_NAME_WHITESPACE, \" \")\n\t\t\t.trim()\n\t\t\t.replace(EXCEL_SHEET_NAME_EDGE_APOSTROPHES, \"\")\n\t\t\t.trim();\n\t\tconst baseName = normalizedName || fallbackName;\n\t\tlet resolvedName = baseName.slice(0, EXCEL_SHEET_NAME_MAX_LENGTH);\n\t\tlet duplicateIndex = 2;\n\n\t\twhile (usedNames.has(resolvedName.toLocaleLowerCase())) {\n\t\t\tconst suffix = ` (${duplicateIndex})`;\n\t\t\tresolvedName = `${baseName.slice(0, EXCEL_SHEET_NAME_MAX_LENGTH - suffix.length)}${suffix}`;\n\t\t\tduplicateIndex += 1;\n\t\t}\n\n\t\tusedNames.add(resolvedName.toLocaleLowerCase());\n\t\tresolvedNames.push(resolvedName);\n\t}\n\n\treturn resolvedNames;\n}\n\n/**\n * Приводит входное значение к типу Excel-ячейки.\n *\n * Важный смысл: если колонка числовая, в файл должен попасть настоящий Number,\n * а не уже отформатированная строка. Тогда Excel сможет применять `format`.\n */\nfunction normalizeCellValue(value: unknown, type: ExcelColumn[\"type\"]) {\n\tif (value == null || value === \"\") return undefined;\n\n\tif (type === Number) {\n\t\tconst numberValue = typeof value === \"number\" ? value : Number(value);\n\t\treturn Number.isFinite(numberValue) ? numberValue : undefined;\n\t}\n\n\tif (type === String) return String(value);\n\tif (type === Boolean) return Boolean(value);\n\tif (type === Date) return value instanceof Date ? value : (parseDate(value) ?? undefined);\n\n\treturn value as CellObject[\"value\"];\n}\n\n/**\n * Собирает SheetData для `write-excel-file`.\n *\n * Функция намеренно не знает о бизнес-сущностях: ей передают уже готовые\n * колонки/строки и необязательный callback стилей конкретных ячеек.\n */\nexport function buildExcelSheetData(args: Omit<DownloadExcelFileArgs, \"fileName\" | \"autoFilter\">): SheetData {\n\tconst headerRow: Cell[] = args.columns.map((column) => ({\n\t\ttype: String,\n\t\tvalue: column.header,\n\t\t...HEADER_CELL_STYLE\n\t}));\n\n\tconst sheetRows: SheetData = [headerRow];\n\n\tfor (let rowIndex = 0; rowIndex < args.rows.length; rowIndex++) {\n\t\tconst row = args.rows[rowIndex];\n\t\tconst sheetRow: Cell[] = [];\n\n\t\tfor (let columnIndex = 0; columnIndex < args.columns.length; columnIndex++) {\n\t\t\tconst column = args.columns[columnIndex];\n\t\t\tconst value = normalizeCellValue(row[column.id], column.type);\n\t\t\tconst style = args.getCellStyle?.({ row, column, rowIndex, columnIndex });\n\n\t\t\tsheetRow.push({\n\t\t\t\tvalue,\n\t\t\t\ttype: column.type,\n\t\t\t\tformat: column.format,\n\t\t\t\t...style\n\t\t\t});\n\t\t}\n\n\t\tsheetRows.push(sheetRow);\n\t}\n\n\treturn sheetRows;\n}\n\n/**\n * Формирует и скачивает Excel-книгу с независимыми табличными листами.\n *\n * Каждый лист получает собственные ширины колонок, стили ячеек и диапазон\n * автофильтра. Доменная подготовка колонок и строк остаётся у вызывающей стороны.\n */\nexport async function downloadExcelWorkbook(args: DownloadExcelWorkbookArgs): Promise<void> {\n\tif (args.sheets.length === 0) {\n\t\tthrow new Error(\"Для формирования Excel-книги нужен минимум один лист\");\n\t}\n\n\tconst sheetNames = resolveExcelSheetNames(args.sheets.map((sheet) => sheet.name));\n\tconst autoFilterRefs: Array<string | undefined> = [];\n\tconst sheets: Sheet<BrowserExcelFileContent>[] = args.sheets.map((sheet, index) => {\n\t\tconst data = buildExcelSheetData(sheet);\n\t\tautoFilterRefs.push(sheet.autoFilter ? buildExcelAutoFilterRef(sheet.columns.length, data.length) : undefined);\n\n\t\treturn {\n\t\t\tdata,\n\t\t\tsheet: sheetNames[index],\n\t\t\tcolumns: sheet.columns.map((column) => ({\n\t\t\t\twidth: column.width\n\t\t\t}))\n\t\t};\n\t});\n\tconst autoFilterFeature = createAutoFilterFeature<BrowserExcelFileContent>(autoFilterRefs);\n\n\tawait writeXlsxFile(sheets, {\n\t\tfeatures: autoFilterFeature ? [autoFilterFeature] : undefined\n\t}).toFile(args.fileName);\n}\n\n/**\n * Формирует и скачивает `.xlsx` в браузере.\n *\n * Это публичная generic-точка shared-слоя: она отвечает только за Excel-файл,\n * а подготовка доменных данных остаётся на вызывающей стороне.\n */\nexport async function downloadExcelFile(args: DownloadExcelFileArgs): Promise<void> {\n\tawait downloadExcelWorkbook({\n\t\tfileName: args.fileName,\n\t\tsheets: [\n\t\t\t{\n\t\t\t\tname: \"Sheet1\",\n\t\t\t\tcolumns: args.columns,\n\t\t\t\trows: args.rows,\n\t\t\t\tautoFilter: args.autoFilter,\n\t\t\t\tgetCellStyle: args.getCellStyle\n\t\t\t}\n\t\t]\n\t});\n}\n","import { DEFAULT_DATE_PRESET_NAMES, getDatePreset, type DateFormatPreset } from \"../formatters/date\";\nimport { getNumberPreset } from \"../formatters/number\";\nimport {\n\tvalidateFormattersPipelineConfig,\n\ttype FormattersPipelineConfig,\n\ttype FormattersPipelineTypedValueFormatConfig\n} from \"../formatters/pipeline\";\nimport {\n\tisODataBooleanType,\n\tisODataDateType,\n\tisODataIntegerType,\n\tisODataNumericType,\n\ttype ColumnRole,\n\ttype ODataMetaType\n} from \"../odata-service\";\n\nimport { downloadExcelFile, downloadExcelWorkbook, type ExcelCellStyle, type ExcelColumn, type ExcelRow } from \"./excel\";\n\nexport type TableExcelColumnDescriptor = {\n\tid: string;\n\theader: string;\n\twidth?: number;\n\trole?: ColumnRole;\n\todataType?: ODataMetaType;\n\tclientOnly?: boolean;\n\tformattersPipeline?: FormattersPipelineConfig;\n};\n\nexport type ResolvedExcelTable = {\n\tcolumns: ExcelColumn[];\n\trows: ExcelRow[];\n\tcellStyles?: ReadonlyArray<Readonly<Record<string, ExcelCellStyle | undefined>>>;\n};\n\nexport type DownloadResolvedExcelTableArgs = {\n\tfileName: string;\n\ttable: ResolvedExcelTable;\n\tautoFilter?: boolean;\n};\n\n/** Один подготовленный табличный лист generic Excel-книги. */\nexport type ResolvedExcelWorkbookSheet = {\n\t/** Желаемое имя листа. */\n\tname: string;\n\t/** Подготовленные колонки, строки и optional стили ячеек. */\n\ttable: ResolvedExcelTable;\n\t/** Включить автофильтр для этого листа. По умолчанию включён. */\n\tautoFilter?: boolean;\n};\n\n/** Аргументы скачивания книги из нескольких подготовленных таблиц. */\nexport type DownloadResolvedExcelWorkbookArgs = {\n\t/** Имя скачиваемого `.xlsx` файла. */\n\tfileName: string;\n\t/** Табличные листы в порядке отображения в книге. */\n\tsheets: ResolvedExcelWorkbookSheet[];\n};\n\nconst INTEGER_EXCEL_FORMAT = \"#,##0\";\nconst DEFAULT_DECIMAL_EXCEL_FORMAT = \"#,##0.0\";\nconst EXCEL_DATE_FORMAT = \"dd.mm.yyyy\";\n/**\n * Excel-маски календарных пресетов сохраняют экранную детализацию:\n * month-* выводит день и месяц без года, а month-year-* — месяц и год без дня.\n */\nconst EXCEL_DAY_MONTH_SHORT_FORMAT = \"dd.mm\";\nconst EXCEL_DAY_MONTH_MEDIUM_FORMAT = \"d mmm\";\nconst EXCEL_DAY_MONTH_LONG_FORMAT = \"d mmmm\";\nconst EXCEL_MONTH_YEAR_MEDIUM_FORMAT = \"mmm yyyy\";\nconst EXCEL_MONTH_YEAR_LONG_FORMAT = \"mmmm yyyy\";\nconst EXCEL_TIME_FORMAT = \"hh:mm\";\nconst EXCEL_TIME_SECONDS_FORMAT = \"hh:mm:ss\";\nconst EXCEL_DATETIME_FORMAT = `${EXCEL_DATE_FORMAT} ${EXCEL_TIME_FORMAT}`;\nconst EXCEL_DATETIME_SECONDS_FORMAT = `${EXCEL_DATE_FORMAT} ${EXCEL_TIME_SECONDS_FORMAT}`;\n\nfunction resolveTypedValueFormatConfig(\n\tformattersPipeline: FormattersPipelineConfig | undefined\n): FormattersPipelineTypedValueFormatConfig | undefined {\n\tconst validation = validateFormattersPipelineConfig(formattersPipeline);\n\tconst typedValueFormatStep = validation.plan?.steps.find((step) => step.type === \"typedValueFormat\");\n\n\treturn typedValueFormatStep?.type === \"typedValueFormat\" ? typedValueFormatStep.config : undefined;\n}\n\nexport function resolveTableExcelColumnType(descriptor: TableExcelColumnDescriptor): ExcelColumn[\"type\"] {\n\tif (descriptor.clientOnly) return Number;\n\tif (isODataDateType(descriptor.odataType)) return Date;\n\tif (isODataBooleanType(descriptor.odataType)) return Boolean;\n\tif (isODataNumericType(descriptor.odataType)) return Number;\n\tif (descriptor.role === \"measure\") return Number;\n\treturn String;\n}\n\nfunction resolveNumberPresetName(descriptor: TableExcelColumnDescriptor): string {\n\tconst configuredPresetName = resolveTypedValueFormatConfig(descriptor.formattersPipeline)?.numberPresetName?.trim();\n\tif (configuredPresetName && getNumberPreset(configuredPresetName)) return configuredPresetName;\n\tif (isODataIntegerType(descriptor.odataType)) return \"integer\";\n\treturn \"decimal\";\n}\n\nfunction resolveExcelNumberFormat(descriptor: TableExcelColumnDescriptor): string {\n\tconst preset = getNumberPreset(resolveNumberPresetName(descriptor));\n\tif (!preset) return DEFAULT_DECIMAL_EXCEL_FORMAT;\n\treturn preset.decimals > 0 ? `#,##0.${\"0\".repeat(preset.decimals)}` : INTEGER_EXCEL_FORMAT;\n}\n\nfunction resolveDatePresetName(descriptor: TableExcelColumnDescriptor): string {\n\tconst configuredPresetName = resolveTypedValueFormatConfig(descriptor.formattersPipeline)?.datePresetName?.trim();\n\tif (configuredPresetName) return configuredPresetName;\n\tif (descriptor.odataType === \"time\") return DEFAULT_DATE_PRESET_NAMES.time;\n\tif (descriptor.odataType === \"datetime\" || descriptor.odataType === \"datetimeOffset\") return DEFAULT_DATE_PRESET_NAMES.datetime;\n\treturn DEFAULT_DATE_PRESET_NAMES.date;\n}\n\nfunction normalizeDatePatternForExcel(pattern: string): string {\n\treturn pattern.replace(/H/g, \"h\").replace(/M/g, \"m\");\n}\n\nfunction resolveBuiltinDatePresetExcelFormat(presetName: string): string | undefined {\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.odataDatetime || presetName === DEFAULT_DATE_PRESET_NAMES.abapDatetime) {\n\t\treturn EXCEL_DATETIME_SECONDS_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.odataDate || presetName === DEFAULT_DATE_PRESET_NAMES.abapDate) {\n\t\treturn EXCEL_DATE_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.monthShort) {\n\t\treturn EXCEL_DAY_MONTH_SHORT_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.monthMedium) {\n\t\treturn EXCEL_DAY_MONTH_MEDIUM_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.monthLong) {\n\t\treturn EXCEL_DAY_MONTH_LONG_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.monthYearMedium) {\n\t\treturn EXCEL_MONTH_YEAR_MEDIUM_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.monthYearLong) {\n\t\treturn EXCEL_MONTH_YEAR_LONG_FORMAT;\n\t}\n\n\tif (presetName.startsWith(\"datetime\")) {\n\t\treturn presetName === DEFAULT_DATE_PRESET_NAMES.datetime || presetName === DEFAULT_DATE_PRESET_NAMES.datetimeShort\n\t\t\t? EXCEL_DATETIME_FORMAT\n\t\t\t: EXCEL_DATETIME_SECONDS_FORMAT;\n\t}\n\n\tif (presetName.startsWith(\"time\")) {\n\t\treturn presetName === DEFAULT_DATE_PRESET_NAMES.time || presetName === DEFAULT_DATE_PRESET_NAMES.timeShort\n\t\t\t? EXCEL_TIME_FORMAT\n\t\t\t: EXCEL_TIME_SECONDS_FORMAT;\n\t}\n\n\tif (presetName.startsWith(\"date\")) return EXCEL_DATE_FORMAT;\n\n\treturn undefined;\n}\n\nfunction resolveExcelDateFormatFromPreset(presetName: string, preset: DateFormatPreset | undefined): string {\n\tconst builtinFormat = resolveBuiltinDatePresetExcelFormat(presetName);\n\tif (builtinFormat) return builtinFormat;\n\tif (preset?.pattern) return normalizeDatePatternForExcel(preset.pattern);\n\n\t// Настройка datePresetName исторически хранит и ручной шаблон. Если имени\n\t// нет в реестре, передаём шаблон в Excel как есть после нормализации токенов.\n\treturn normalizeDatePatternForExcel(presetName);\n}\n\nfunction resolveExcelDateFormat(descriptor: TableExcelColumnDescriptor): string {\n\tconst presetName = resolveDatePresetName(descriptor);\n\treturn resolveExcelDateFormatFromPreset(presetName, getDatePreset(presetName));\n}\n\nfunction resolveTableExcelColumnFormat(descriptor: TableExcelColumnDescriptor, type: ExcelColumn[\"type\"]): string | undefined {\n\tif (type === Number) return resolveExcelNumberFormat(descriptor);\n\tif (type === Date) return resolveExcelDateFormat(descriptor);\n\treturn undefined;\n}\n\nexport function createTableExcelColumn(descriptor: TableExcelColumnDescriptor): ExcelColumn {\n\tconst type = resolveTableExcelColumnType(descriptor);\n\treturn {\n\t\tid: descriptor.id,\n\t\theader: descriptor.header,\n\t\twidth: descriptor.width,\n\t\ttype,\n\t\tformat: resolveTableExcelColumnFormat(descriptor, type)\n\t};\n}\n\nexport async function downloadResolvedExcelTable({ fileName, table, autoFilter = true }: DownloadResolvedExcelTableArgs): Promise<void> {\n\tawait downloadExcelFile({\n\t\tfileName,\n\t\tcolumns: table.columns,\n\t\trows: table.rows,\n\t\tautoFilter,\n\t\tgetCellStyle: ({ rowIndex, column }) => table.cellStyles?.[rowIndex]?.[column.id]\n\t});\n}\n\n/** Скачивает многолистовую книгу из таблиц, уже подготовленных доменными consumers. */\nexport async function downloadResolvedExcelWorkbook({ fileName, sheets }: DownloadResolvedExcelWorkbookArgs): Promise<void> {\n\tawait downloadExcelWorkbook({\n\t\tfileName,\n\t\tsheets: sheets.map(({ name, table, autoFilter = true }) => ({\n\t\t\tname,\n\t\t\tcolumns: table.columns,\n\t\t\trows: table.rows,\n\t\t\tautoFilter,\n\t\t\tgetCellStyle: ({ rowIndex, column }) => table.cellStyles?.[rowIndex]?.[column.id]\n\t\t}))\n\t});\n}\n"],"mappings":";;;;AAyCA,IAAM,IAA0D;CAC/D,aAAa;CACb,SAAS;CACT,SAAS;CACT,OAAO;AACR,GAEM,IAAoB;CACzB,YAAY;CACZ,mBAAmB;CACnB,mBAAmB;AACpB,GAEM,IAA8B,IAC9B,IAAsC,8BACtC,IAAoC,YACpC,IAA8B;AAQpC,SAAgB,EAA+B,GAAsD;CACpG,IAAM,IAAY,IAAQ,EAAwB,KAAS,KAAA;CAC3D,OAAO,IAAY,EAAE,aAAU,IAAI,KAAA;AACpC;AAOA,SAAS,EAAmB,GAAuB;CAClD,IAAI,IAAQ,IAAQ,GAChB,IAAM;CAEV,OAAO,IAAQ,IAAG;EACjB,IAAM,KAAa,IAAQ,KAAK;EAEhC,AADA,IAAM,OAAO,aAAa,KAAK,CAAS,IAAI,GAC5C,IAAQ,KAAK,OAAO,IAAQ,KAAK,EAAE;CACpC;CAEA,OAAO;AACR;AAQA,SAAgB,EAAwB,GAAqB,GAAsC;CAC9F,WAAe,KAAK,KAAY,IAEpC,OAAO,MAAM,EAAmB,IAAc,CAAC,IAAI;AACpD;AAQA,SAAS,EAAqC,GAAyE;CACjH,MAAK,KAAK,OAAO,GAEtB,OAAO,EACN,OAAO,EACN,WAAW,EACV,+BAA+B,EAC9B,YAAY,GAAS,GAAU,MAAe;EAC7C,IAAM,IAAM,EAAK,EAAW;EAC5B,OAAO,IAAM,EAAQ,QAAQ,gBAAgB,gCAAgC,EAAI,IAAI,IAAI;CAC1F,EACD,EACD,EACD,EACD;AACD;AASA,SAAgB,EAAuB,GAAoC;CAC1E,IAAM,oBAAY,IAAI,IAAY,GAC5B,IAA0B,CAAC;CAEjC,KAAK,IAAI,IAAQ,GAAG,IAAQ,EAAM,QAAQ,KAAS,GAAG;EACrD,IAAM,IAAe,QAAQ,IAAQ,KAO/B,IANiB,EAAM,EAAM,EAChC,QAAQ,GAAqC,GAAG,CAAC,CAClD,QAAQ,GAA6B,GAAG,CAAC,CACzC,KAAK,CAAC,CACN,QAAQ,GAAmC,EAAE,CAAC,CAC9C,KAAK,KAC4B,GAC/B,IAAe,EAAS,MAAM,GAAG,CAA2B,GAC5D,IAAiB;EAErB,OAAO,EAAU,IAAI,EAAa,kBAAkB,CAAC,IAAG;GACvD,IAAM,IAAS,KAAK,EAAe;GAEnC,AADA,IAAe,GAAG,EAAS,MAAM,GAAG,IAA8B,EAAO,MAAM,IAAI,KACnF,KAAkB;EACnB;EAGA,AADA,EAAU,IAAI,EAAa,kBAAkB,CAAC,GAC9C,EAAc,KAAK,CAAY;CAChC;CAEA,OAAO;AACR;AAQA,SAAS,EAAmB,GAAgB,GAA2B;CAClE,SAAS,QAAQ,MAAU,IAE/B;MAAI,MAAS,QAAQ;GACpB,IAAM,IAAc,OAAO,KAAU,WAAW,IAAQ,OAAO,CAAK;GACpE,OAAO,OAAO,SAAS,CAAW,IAAI,IAAc,KAAA;EACrD;EAMA,OAJI,MAAS,SAAe,OAAO,CAAK,IACpC,MAAS,UAAgB,EAAQ,IACjC,MAAS,OAAa,aAAiB,OAAO,IAAS,EAAU,CAAK,KAAK,KAAA,IAExE;CANP;AAOD;AAQA,SAAgB,EAAoB,GAAyE;CAO5G,IAAM,IAAuB,CANH,EAAK,QAAQ,KAAK,OAAY;EACvD,MAAM;EACN,OAAO,EAAO;EACd,GAAG;CACJ,EAE8B,CAAS;CAEvC,KAAK,IAAI,IAAW,GAAG,IAAW,EAAK,KAAK,QAAQ,KAAY;EAC/D,IAAM,IAAM,EAAK,KAAK,IAChB,IAAmB,CAAC;EAE1B,KAAK,IAAI,IAAc,GAAG,IAAc,EAAK,QAAQ,QAAQ,KAAe;GAC3E,IAAM,IAAS,EAAK,QAAQ,IACtB,IAAQ,EAAmB,EAAI,EAAO,KAAK,EAAO,IAAI,GACtD,IAAQ,EAAK,eAAe;IAAE;IAAK;IAAQ;IAAU;GAAY,CAAC;GAExE,EAAS,KAAK;IACb;IACA,MAAM,EAAO;IACb,QAAQ,EAAO;IACf,GAAG;GACJ,CAAC;EACF;EAEA,EAAU,KAAK,CAAQ;CACxB;CAEA,OAAO;AACR;AAQA,eAAsB,EAAsB,GAAgD;CAC3F,IAAI,EAAK,OAAO,WAAW,GAC1B,MAAU,MAAM,sDAAsD;CAGvE,IAAM,IAAa,EAAuB,EAAK,OAAO,KAAK,MAAU,EAAM,IAAI,CAAC,GAC1E,IAA4C,CAAC,GAC7C,IAA2C,EAAK,OAAO,KAAK,GAAO,MAAU;EAClF,IAAM,IAAO,EAAoB,CAAK;EAGtC,OAFA,EAAe,KAAK,EAAM,aAAa,EAAwB,EAAM,QAAQ,QAAQ,EAAK,MAAM,IAAI,KAAA,CAAS,GAEtG;GACN;GACA,OAAO,EAAW;GAClB,SAAS,EAAM,QAAQ,KAAK,OAAY,EACvC,OAAO,EAAO,MACf,EAAE;EACH;CACD,CAAC,GACK,IAAoB,EAAiD,CAAc;CAEzF,MAAM,EAAc,GAAQ,EAC3B,UAAU,IAAoB,CAAC,CAAiB,IAAI,KAAA,EACrD,CAAC,CAAC,CAAC,OAAO,EAAK,QAAQ;AACxB;AAQA,eAAsB,EAAkB,GAA4C;CACnF,MAAM,EAAsB;EAC3B,UAAU,EAAK;EACf,QAAQ,CACP;GACC,MAAM;GACN,SAAS,EAAK;GACd,MAAM,EAAK;GACX,YAAY,EAAK;GACjB,cAAc,EAAK;EACpB,CACD;CACD,CAAC;AACF;;;ACnNA,IAAM,IAAuB,SACvB,IAA+B,WAC/B,IAAoB,cAKpB,IAA+B,SAC/B,IAAgC,SAChC,IAA8B,UAC9B,IAAiC,YACjC,IAA+B,aAC/B,IAAoB,SACpB,IAA4B,YAC5B,IAAwB,GAAG,EAAkB,GAAG,KAChD,IAAgC,GAAG,EAAkB,GAAG;AAE9D,SAAS,EACR,GACuD;CAEvD,IAAM,IADa,EAAiC,CACvB,CAAA,CAAW,MAAM,MAAM,MAAM,MAAS,EAAK,SAAS,kBAAkB;CAEnG,OAAO,GAAsB,SAAS,qBAAqB,EAAqB,SAAS,KAAA;AAC1F;AAEA,SAAgB,EAA4B,GAA6D;CAMxG,OALI,EAAW,aAAmB,SAC9B,EAAgB,EAAW,SAAS,IAAU,OAC9C,EAAmB,EAAW,SAAS,IAAU,UACjD,EAAmB,EAAW,SAAS,KACvC,EAAW,SAAS,YAAkB,SACnC;AACR;AAEA,SAAS,EAAwB,GAAgD;CAChF,IAAM,IAAuB,EAA8B,EAAW,kBAAkB,CAAC,EAAE,kBAAkB,KAAK;CAGlH,OAFI,KAAwB,EAAgB,CAAoB,IAAU,IACtE,EAAmB,EAAW,SAAS,IAAU,YAC9C;AACR;AAEA,SAAS,EAAyB,GAAgD;CACjF,IAAM,IAAS,EAAgB,EAAwB,CAAU,CAAC;CAElE,OADK,IACE,EAAO,WAAW,IAAI,SAAS,IAAI,OAAO,EAAO,QAAQ,MAAM,IADlD;AAErB;AAEA,SAAS,EAAsB,GAAgD;CAK9E,OAJ6B,EAA8B,EAAW,kBAAkB,CAAC,EAAE,gBAAgB,KAAK,MAE5G,EAAW,cAAc,SAAe,EAA0B,OAClE,EAAW,cAAc,cAAc,EAAW,cAAc,mBAAyB,EAA0B,WAChH,EAA0B;AAClC;AAEA,SAAS,EAA6B,GAAyB;CAC9D,OAAO,EAAQ,QAAQ,MAAM,GAAG,CAAC,CAAC,QAAQ,MAAM,GAAG;AACpD;AAEA,SAAS,EAAoC,GAAwC;CACpF,IAAI,MAAe,EAA0B,iBAAiB,MAAe,EAA0B,cACtG,OAAO;CAGR,IAAI,MAAe,EAA0B,aAAa,MAAe,EAA0B,UAClG,OAAO;CAGR,IAAI,MAAe,EAA0B,YAC5C,OAAO;CAGR,IAAI,MAAe,EAA0B,aAC5C,OAAO;CAGR,IAAI,MAAe,EAA0B,WAC5C,OAAO;CAGR,IAAI,MAAe,EAA0B,iBAC5C,OAAO;CAGR,IAAI,MAAe,EAA0B,eAC5C,OAAO;CAGR,IAAI,EAAW,WAAW,UAAU,GACnC,OAAO,MAAe,EAA0B,YAAY,MAAe,EAA0B,gBAClG,IACA;CAGJ,IAAI,EAAW,WAAW,MAAM,GAC/B,OAAO,MAAe,EAA0B,QAAQ,MAAe,EAA0B,YAC9F,IACA;CAGJ,IAAI,EAAW,WAAW,MAAM,GAAG,OAAO;AAG3C;AAEA,SAAS,EAAiC,GAAoB,GAA8C;CAO3G,OANsB,EAAoC,CACtD,MACA,GAAQ,UAAgB,EAA6B,EAAO,OAAO,IAIhE,EAA6B,CAAU;AAC/C;AAEA,SAAS,EAAuB,GAAgD;CAC/E,IAAM,IAAa,EAAsB,CAAU;CACnD,OAAO,EAAiC,GAAY,EAAc,CAAU,CAAC;AAC9E;AAEA,SAAS,EAA8B,GAAwC,GAA+C;CAC7H,IAAI,MAAS,QAAQ,OAAO,EAAyB,CAAU;CAC/D,IAAI,MAAS,MAAM,OAAO,EAAuB,CAAU;AAE5D;AAEA,SAAgB,EAAuB,GAAqD;CAC3F,IAAM,IAAO,EAA4B,CAAU;CACnD,OAAO;EACN,IAAI,EAAW;EACf,QAAQ,EAAW;EACnB,OAAO,EAAW;EAClB;EACA,QAAQ,EAA8B,GAAY,CAAI;CACvD;AACD;AAEA,eAAsB,EAA2B,EAAE,aAAU,UAAO,gBAAa,MAAuD;CACvI,MAAM,EAAkB;EACvB;EACA,SAAS,EAAM;EACf,MAAM,EAAM;EACZ;EACA,eAAe,EAAE,aAAU,gBAAa,EAAM,aAAa,EAAS,GAAG,EAAO;CAC/E,CAAC;AACF;AAGA,eAAsB,EAA8B,EAAE,aAAU,aAA4D;CAC3H,MAAM,EAAsB;EAC3B;EACA,QAAQ,EAAO,KAAK,EAAE,SAAM,UAAO,gBAAa,UAAY;GAC3D;GACA,SAAS,EAAM;GACf,MAAM,EAAM;GACZ;GACA,eAAe,EAAE,aAAU,gBAAa,EAAM,aAAa,EAAS,GAAG,EAAO;EAC/E,EAAE;CACH,CAAC;AACF"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/excel/excel.ts","../../src/excel/matrixWorkbook.ts","../../src/excel/tableExport.ts"],"sourcesContent":["import writeXlsxFile, { type Cell, type CellObject, type Feature, type Sheet, type SheetData } from \"write-excel-file/browser\";\n\nimport { parseDate } from \"../formatters/date\";\nimport { type State } from \"../types\";\n\nexport type ExcelColumn = {\n\tid: string;\n\theader: string;\n\twidth?: number;\n\ttype?: StringConstructor | NumberConstructor | DateConstructor | BooleanConstructor;\n\tformat?: string;\n};\n\nexport type ExcelRow = Record<string, unknown>;\n\nexport type ExcelCellStyle = Omit<CellObject, \"value\" | \"type\" | \"format\">;\n\nexport type DownloadExcelFileArgs = {\n\tfileName: string;\n\tcolumns: ExcelColumn[];\n\trows: ExcelRow[];\n\tautoFilter?: boolean;\n\tgetCellStyle?: (args: { row: ExcelRow; column: ExcelColumn; rowIndex: number; columnIndex: number }) => ExcelCellStyle | undefined;\n};\n\n/** Описание одного табличного листа внутри Excel-книги. */\nexport type ExcelWorkbookSheet = Omit<DownloadExcelFileArgs, \"fileName\"> & {\n\t/** Желаемое имя листа; writer нормализует ограничения формата и конфликты имён. */\n\tname: string;\n};\n\n/** Аргументы формирования Excel-книги с одним или несколькими табличными листами. */\nexport type DownloadExcelWorkbookArgs = {\n\t/** Имя скачиваемого `.xlsx` файла. */\n\tfileName: string;\n\t/** Листы книги в порядке отображения в Excel. */\n\tsheets: ExcelWorkbookSheet[];\n};\n\ntype BrowserExcelFileContent = File | Blob | ArrayBuffer;\n\nconst VALUE_STATE_TEXT_COLORS: Partial<Record<State, string>> = {\n\tinformation: \"#2563EB\",\n\tsuccess: \"#107C10\",\n\twarning: \"#B7791F\",\n\terror: \"#C50F1F\"\n};\n\nconst HEADER_CELL_STYLE = {\n\tfontWeight: \"bold\",\n\tbottomBorderStyle: \"thin\",\n\tbottomBorderColor: \"#D9D9D9\"\n} satisfies ExcelCellStyle;\n\nconst EXCEL_SHEET_NAME_MAX_LENGTH = 31;\nconst EXCEL_SHEET_NAME_INVALID_CHARACTERS = /[\\u0000-\\u001f[\\]/\\\\:*?]+/g;\nconst EXCEL_SHEET_NAME_EDGE_APOSTROPHES = /^'+|'+$/g;\nconst EXCEL_SHEET_NAME_WHITESPACE = /\\s+/g;\n\n/**\n * Приводит общий value-state проекта к базовому Excel-стилю.\n *\n * Helper живет в shared Excel-слое, чтобы analytical/detail экспорт не\n * дублировали палитру статусов и одинаково подсвечивали warning/error/success.\n */\nexport function resolveExcelCellStyleFromState(state: State | undefined): ExcelCellStyle | undefined {\n\tconst textColor = state ? VALUE_STATE_TEXT_COLORS[state] : undefined;\n\treturn textColor ? { textColor } : undefined;\n}\n\n/**\n * Переводит zero-based индекс колонки в Excel-адрес (`0 -> A`, `27 -> AB`).\n * Нужен для XML-диапазонов, потому что `write-excel-file` не предоставляет\n * отдельного API для построения ссылок вида `A1:C10`.\n */\nfunction numberToColumnName(index: number): string {\n\tlet value = index + 1;\n\tlet out = \"\";\n\n\twhile (value > 0) {\n\t\tconst remainder = (value - 1) % 26;\n\t\tout = String.fromCharCode(65 + remainder) + out;\n\t\tvalue = Math.floor((value - 1) / 26);\n\t}\n\n\treturn out;\n}\n\n/**\n * Строит диапазон автофильтра для всей выгруженной таблицы.\n *\n * Возвращает `undefined` для пустой сетки, чтобы не вставлять в xlsx\n * невалидный `<autoFilter>` без реального диапазона.\n */\nexport function buildExcelAutoFilterRef(columnCount: number, rowCount: number): string | undefined {\n\tif (columnCount <= 0 || rowCount <= 0) return undefined;\n\n\treturn `A1:${numberToColumnName(columnCount - 1)}${rowCount}`;\n}\n\n/**\n * Добавляет autoFilter через feature-transform `write-excel-file`.\n *\n * Библиотека умеет трансформировать XML листа, но не имеет высокоуровневого\n * параметра `autoFilter`, поэтому здесь изолирован низкоуровневый OpenXML-хук.\n */\nfunction createAutoFilterFeature<FileContent>(refs: readonly (string | undefined)[]): Feature<FileContent> | undefined {\n\tif (!refs.some(Boolean)) return undefined;\n\n\treturn {\n\t\tfiles: {\n\t\t\ttransform: {\n\t\t\t\t\"xl/worksheets/sheet{id}.xml\": {\n\t\t\t\t\ttransform: (content, _options, properties) => {\n\t\t\t\t\t\tconst ref = refs[properties.sheetIndex];\n\t\t\t\t\t\treturn ref ? content.replace(\"</sheetData>\", `</sheetData><autoFilter ref=\"${ref}\"/>`) : content;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\n/**\n * Нормализует имена листов по ограничениям Excel и делает их уникальными.\n *\n * Runtime-заголовки могут содержать запрещённые символы, превышать 31 знак или\n * совпадать между несколькими представлениями одной конфигурации. Нормализация\n * выполняется централизованно, чтобы все consumers получали открываемую книгу.\n */\nexport function resolveExcelSheetNames(names: readonly string[]): string[] {\n\tconst usedNames = new Set<string>();\n\tconst resolvedNames: string[] = [];\n\n\tfor (let index = 0; index < names.length; index += 1) {\n\t\tconst fallbackName = `Лист ${index + 1}`;\n\t\tconst normalizedName = names[index]\n\t\t\t?.replace(EXCEL_SHEET_NAME_INVALID_CHARACTERS, \" \")\n\t\t\t.replace(EXCEL_SHEET_NAME_WHITESPACE, \" \")\n\t\t\t.trim()\n\t\t\t.replace(EXCEL_SHEET_NAME_EDGE_APOSTROPHES, \"\")\n\t\t\t.trim();\n\t\tconst baseName = normalizedName || fallbackName;\n\t\tlet resolvedName = baseName.slice(0, EXCEL_SHEET_NAME_MAX_LENGTH);\n\t\tlet duplicateIndex = 2;\n\n\t\twhile (usedNames.has(resolvedName.toLocaleLowerCase())) {\n\t\t\tconst suffix = ` (${duplicateIndex})`;\n\t\t\tresolvedName = `${baseName.slice(0, EXCEL_SHEET_NAME_MAX_LENGTH - suffix.length)}${suffix}`;\n\t\t\tduplicateIndex += 1;\n\t\t}\n\n\t\tusedNames.add(resolvedName.toLocaleLowerCase());\n\t\tresolvedNames.push(resolvedName);\n\t}\n\n\treturn resolvedNames;\n}\n\n/**\n * Приводит входное значение к типу Excel-ячейки.\n *\n * Важный смысл: если колонка числовая, в файл должен попасть настоящий Number,\n * а не уже отформатированная строка. Тогда Excel сможет применять `format`.\n */\nfunction normalizeCellValue(value: unknown, type: ExcelColumn[\"type\"]) {\n\tif (value == null || value === \"\") return undefined;\n\n\tif (type === Number) {\n\t\tconst numberValue = typeof value === \"number\" ? value : Number(value);\n\t\treturn Number.isFinite(numberValue) ? numberValue : undefined;\n\t}\n\n\tif (type === String) return String(value);\n\tif (type === Boolean) return Boolean(value);\n\tif (type === Date) return value instanceof Date ? value : (parseDate(value) ?? undefined);\n\n\treturn value as CellObject[\"value\"];\n}\n\n/**\n * Собирает SheetData для `write-excel-file`.\n *\n * Функция намеренно не знает о бизнес-сущностях: ей передают уже готовые\n * колонки/строки и необязательный callback стилей конкретных ячеек.\n */\nexport function buildExcelSheetData(args: Omit<DownloadExcelFileArgs, \"fileName\" | \"autoFilter\">): SheetData {\n\tconst headerRow: Cell[] = args.columns.map((column) => ({\n\t\ttype: String,\n\t\tvalue: column.header,\n\t\t...HEADER_CELL_STYLE\n\t}));\n\n\tconst sheetRows: SheetData = [headerRow];\n\n\tfor (let rowIndex = 0; rowIndex < args.rows.length; rowIndex++) {\n\t\tconst row = args.rows[rowIndex];\n\t\tconst sheetRow: Cell[] = [];\n\n\t\tfor (let columnIndex = 0; columnIndex < args.columns.length; columnIndex++) {\n\t\t\tconst column = args.columns[columnIndex];\n\t\t\tconst value = normalizeCellValue(row[column.id], column.type);\n\t\t\tconst style = args.getCellStyle?.({ row, column, rowIndex, columnIndex });\n\n\t\t\tsheetRow.push({\n\t\t\t\tvalue,\n\t\t\t\ttype: column.type,\n\t\t\t\tformat: column.format,\n\t\t\t\t...style\n\t\t\t});\n\t\t}\n\n\t\tsheetRows.push(sheetRow);\n\t}\n\n\treturn sheetRows;\n}\n\n/**\n * Формирует и скачивает Excel-книгу с независимыми табличными листами.\n *\n * Каждый лист получает собственные ширины колонок, стили ячеек и диапазон\n * автофильтра. Доменная подготовка колонок и строк остаётся у вызывающей стороны.\n */\nexport async function downloadExcelWorkbook(args: DownloadExcelWorkbookArgs): Promise<void> {\n\tif (args.sheets.length === 0) {\n\t\tthrow new Error(\"Для формирования Excel-книги нужен минимум один лист\");\n\t}\n\n\tconst sheetNames = resolveExcelSheetNames(args.sheets.map((sheet) => sheet.name));\n\tconst autoFilterRefs: Array<string | undefined> = [];\n\tconst sheets: Sheet<BrowserExcelFileContent>[] = args.sheets.map((sheet, index) => {\n\t\tconst data = buildExcelSheetData(sheet);\n\t\tautoFilterRefs.push(sheet.autoFilter ? buildExcelAutoFilterRef(sheet.columns.length, data.length) : undefined);\n\n\t\treturn {\n\t\t\tdata,\n\t\t\tsheet: sheetNames[index],\n\t\t\tcolumns: sheet.columns.map((column) => ({\n\t\t\t\twidth: column.width\n\t\t\t}))\n\t\t};\n\t});\n\tconst autoFilterFeature = createAutoFilterFeature<BrowserExcelFileContent>(autoFilterRefs);\n\n\tawait writeXlsxFile(sheets, {\n\t\tfeatures: autoFilterFeature ? [autoFilterFeature] : undefined\n\t}).toFile(args.fileName);\n}\n\n/**\n * Формирует и скачивает `.xlsx` в браузере.\n *\n * Это публичная generic-точка shared-слоя: она отвечает только за Excel-файл,\n * а подготовка доменных данных остаётся на вызывающей стороне.\n */\nexport async function downloadExcelFile(args: DownloadExcelFileArgs): Promise<void> {\n\tawait downloadExcelWorkbook({\n\t\tfileName: args.fileName,\n\t\tsheets: [\n\t\t\t{\n\t\t\t\tname: \"Sheet1\",\n\t\t\t\tcolumns: args.columns,\n\t\t\t\trows: args.rows,\n\t\t\t\tautoFilter: args.autoFilter,\n\t\t\t\tgetCellStyle: args.getCellStyle\n\t\t\t}\n\t\t]\n\t});\n}\n","import writeXlsxFile, { type Cell, type Feature, type Sheet } from \"write-excel-file/browser\";\n\nimport { downloadFileFromBlob } from \"../dom\";\nimport { escapeXmlValue } from \"../xml\";\n\nimport { resolveExcelSheetNames } from \"./excel\";\n\ntype BrowserExcelFileContent = File | Blob | ArrayBuffer;\n\n/** Одна строка произвольной Excel-матрицы. Объединения задаются стилями первой ячейки диапазона. */\nexport type ExcelMatrixRow = readonly Cell[];\n\n/** Поля задаются в дюймах, как в OpenXML и диалоге параметров страницы Excel. */\nexport type ExcelPrintMargins = {\n\treadonly left: number;\n\treadonly right: number;\n\treadonly top: number;\n\treadonly bottom: number;\n\treadonly header: number;\n\treadonly footer: number;\n};\n\n/** Видимый колонтитул не принимает raw Excel-коды, чтобы текст не мог случайно изменить разметку секций. */\nexport type ExcelPrintHeaderFooter = {\n\treadonly left?: string;\n\treadonly center?: string;\n\treadonly right?: string;\n\treadonly includePageNumber?: boolean;\n};\n\n/** One-based прямоугольник печати соответствует координатам строк и столбцов Excel. */\nexport type ExcelPrintArea = {\n\treadonly firstRow: number;\n\treadonly lastRow: number;\n\treadonly firstColumn: number;\n\treadonly lastColumn: number;\n};\n\n/**\n * Печатный контракт одного листа.\n *\n * `columnBreaksAfter` и `rowBreaksAfter` используют one-based координату\n * последней ячейки страницы. Явная модель не позволяет consumer перепутать\n * индекс массива с адресом Excel и разрезать составной блок на печати.\n */\nexport type ExcelPrintSetup = {\n\treadonly orientation: \"portrait\" | \"landscape\";\n\treadonly paperSize?: \"a4\" | \"a3\";\n\treadonly fitToWidthPages?: number;\n\treadonly fitToHeightPages?: number;\n\treadonly pageOrder?: \"downThenOver\" | \"overThenDown\";\n\treadonly margins?: ExcelPrintMargins;\n\treadonly repeatRows?: readonly [firstRow: number, lastRow: number];\n\treadonly repeatColumns?: readonly [firstColumn: number, lastColumn: number];\n\treadonly printArea?: ExcelPrintArea;\n\treadonly columnBreaksAfter?: readonly number[];\n\treadonly rowBreaksAfter?: readonly number[];\n\treadonly header?: ExcelPrintHeaderFooter;\n\treadonly footer?: ExcelPrintHeaderFooter;\n};\n\n/** Произвольный лист сохраняет готовую матрицу и не навязывает табличную строку заголовков. */\nexport type ExcelMatrixWorkbookSheet = {\n\treadonly name: string;\n\treadonly rows: readonly ExcelMatrixRow[];\n\treadonly columns?: readonly { readonly width?: number }[];\n\treadonly showGridLines?: boolean;\n\treadonly zoomScale?: number;\n\treadonly print?: ExcelPrintSetup;\n};\n\n/** Аргументы generic-книги, пригодной как для скачивания, так и для file validation. */\nexport type ExcelMatrixWorkbookArgs = {\n\treadonly sheets: readonly ExcelMatrixWorkbookSheet[];\n};\n\nconst DEFAULT_PRINT_MARGINS = {\n\tleft: 0.2,\n\tright: 0.2,\n\ttop: 0.25,\n\tbottom: 0.25,\n\theader: 0.1,\n\tfooter: 0.1\n} as const satisfies ExcelPrintMargins;\n\nconst PAPER_SIZE = { a4: 9, a3: 8 } as const;\n\nfunction escapeHeaderFooterText(value: string): string {\n\treturn escapeXmlValue(value.replaceAll(\"&\", \"&&\"));\n}\n\nfunction numberToColumnName(index: number): string {\n\tlet value = index;\n\tlet result = \"\";\n\twhile (value > 0) {\n\t\tconst remainder = (value - 1) % 26;\n\t\tresult = String.fromCharCode(65 + remainder) + result;\n\t\tvalue = Math.floor((value - 1) / 26);\n\t}\n\treturn result;\n}\n\nfunction assertPositiveInteger(value: number, name: string): void {\n\tif (!Number.isInteger(value) || value <= 0) throw new Error(`${name} должен быть положительным целым числом`);\n}\n\nfunction validateRange(range: readonly [number, number], name: string): void {\n\tassertPositiveInteger(range[0], `${name}: начало`);\n\tassertPositiveInteger(range[1], `${name}: конец`);\n\tif (range[0] > range[1]) throw new Error(`${name}: начало не может быть больше конца`);\n}\n\nfunction validatePrintSetup(print: ExcelPrintSetup): void {\n\tif (print.fitToWidthPages !== undefined) assertPositiveInteger(print.fitToWidthPages, \"fitToWidthPages\");\n\tif (print.fitToHeightPages !== undefined) assertPositiveInteger(print.fitToHeightPages, \"fitToHeightPages\");\n\tif (print.repeatRows) validateRange(print.repeatRows, \"repeatRows\");\n\tif (print.repeatColumns) validateRange(print.repeatColumns, \"repeatColumns\");\n\tfor (const value of print.columnBreaksAfter ?? []) assertPositiveInteger(value, \"columnBreaksAfter\");\n\tfor (const value of print.rowBreaksAfter ?? []) assertPositiveInteger(value, \"rowBreaksAfter\");\n\tif (print.printArea) {\n\t\tassertPositiveInteger(print.printArea.firstRow, \"printArea.firstRow\");\n\t\tassertPositiveInteger(print.printArea.lastRow, \"printArea.lastRow\");\n\t\tassertPositiveInteger(print.printArea.firstColumn, \"printArea.firstColumn\");\n\t\tassertPositiveInteger(print.printArea.lastColumn, \"printArea.lastColumn\");\n\t\tif (print.printArea.firstRow > print.printArea.lastRow || print.printArea.firstColumn > print.printArea.lastColumn) {\n\t\t\tthrow new Error(\"printArea должен задавать непустой прямоугольник\");\n\t\t}\n\t}\n}\n\nfunction buildHeaderFooterXml(\n\ttagName: \"headerFooter\",\n\theader: ExcelPrintHeaderFooter | undefined,\n\tfooter: ExcelPrintHeaderFooter | undefined\n): string {\n\tif (!header && !footer) return \"\";\n\tconst buildSection = (value: ExcelPrintHeaderFooter | undefined) => {\n\t\tif (!value) return \"\";\n\t\tconst content = [\n\t\t\tvalue.left ? `&amp;L${escapeHeaderFooterText(value.left)}` : \"\",\n\t\t\tvalue.center ? `&amp;C${escapeHeaderFooterText(value.center)}` : \"\",\n\t\t\tvalue.right ? `&amp;R${escapeHeaderFooterText(value.right)}` : \"\",\n\t\t\tvalue.includePageNumber ? \"&amp;RСтраница &amp;P из &amp;N\" : \"\"\n\t\t].join(\"\");\n\t\treturn content;\n\t};\n\tconst oddHeader = buildSection(header);\n\tconst oddFooter = buildSection(footer);\n\treturn `<${tagName}>${oddHeader ? `<oddHeader>${oddHeader}</oddHeader>` : \"\"}${oddFooter ? `<oddFooter>${oddFooter}</oddFooter>` : \"\"}</${tagName}>`;\n}\n\nfunction buildBreaksXml(tagName: \"colBreaks\" | \"rowBreaks\", values: readonly number[] | undefined): string {\n\tif (!values || values.length === 0) return \"\";\n\tconst uniqueValues = [...new Set(values)].sort((left, right) => left - right);\n\treturn `<${tagName} count=\"${uniqueValues.length}\" manualBreakCount=\"${uniqueValues.length}\">${uniqueValues\n\t\t.map((value) => `<brk id=\"${value}\" min=\"0\" max=\"16383\" man=\"1\"/>`)\n\t\t.join(\"\")}</${tagName}>`;\n}\n\nfunction applyWorksheetPrintSetup(content: string, print: ExcelPrintSetup): string {\n\tconst margins = print.margins ?? DEFAULT_PRINT_MARGINS;\n\tconst fitToPage = print.fitToWidthPages !== undefined || print.fitToHeightPages !== undefined;\n\tconst sheetProperties = fitToPage ? '<sheetPr><pageSetUpPr fitToPage=\"1\" autoPageBreaks=\"0\"/></sheetPr>' : \"\";\n\tconst result = sheetProperties ? content.replace(\"<sheetViews\", `${sheetProperties}<sheetViews`) : content;\n\tconst pageMargins = `<pageMargins left=\"${margins.left}\" right=\"${margins.right}\" top=\"${margins.top}\" bottom=\"${margins.bottom}\" header=\"${margins.header}\" footer=\"${margins.footer}\"/>`;\n\tconst pageSetupAttributes = [\n\t\t`paperSize=\"${PAPER_SIZE[print.paperSize ?? \"a4\"]}\"`,\n\t\t`orientation=\"${print.orientation}\"`,\n\t\tprint.fitToWidthPages !== undefined ? `fitToWidth=\"${print.fitToWidthPages}\"` : \"\",\n\t\tprint.fitToHeightPages !== undefined ? `fitToHeight=\"${print.fitToHeightPages}\"` : \"\",\n\t\tprint.pageOrder ? `pageOrder=\"${print.pageOrder}\"` : \"\",\n\t\t'usePrinterDefaults=\"0\"'\n\t]\n\t\t.filter(Boolean)\n\t\t.join(\" \");\n\tconst additions = [\n\t\tpageMargins,\n\t\t`<pageSetup ${pageSetupAttributes}/>`,\n\t\tbuildHeaderFooterXml(\"headerFooter\", print.header, print.footer),\n\t\tbuildBreaksXml(\"rowBreaks\", print.rowBreaksAfter),\n\t\tbuildBreaksXml(\"colBreaks\", print.columnBreaksAfter)\n\t].join(\"\");\n\tconst drawingIndex = result.indexOf(\"<drawing\");\n\treturn drawingIndex >= 0\n\t\t? `${result.slice(0, drawingIndex)}${additions}${result.slice(drawingIndex)}`\n\t\t: result.replace(\"</worksheet>\", `${additions}</worksheet>`);\n}\n\nfunction quoteFormulaSheetName(name: string): string {\n\treturn `'${name.replaceAll(\"'\", \"''\")}'`;\n}\n\nfunction buildDefinedNames(sheetNames: readonly string[], sheets: readonly ExcelMatrixWorkbookSheet[]): string {\n\tconst definitions: string[] = [];\n\tfor (let sheetIndex = 0; sheetIndex < sheets.length; sheetIndex += 1) {\n\t\tconst print = sheets[sheetIndex]?.print;\n\t\tif (!print) continue;\n\t\tconst sheetName = quoteFormulaSheetName(sheetNames[sheetIndex] ?? `Лист ${sheetIndex + 1}`);\n\t\tconst titles = [\n\t\t\tprint.repeatRows ? `${sheetName}!$${print.repeatRows[0]}:$${print.repeatRows[1]}` : \"\",\n\t\t\tprint.repeatColumns\n\t\t\t\t? `${sheetName}!$${numberToColumnName(print.repeatColumns[0])}:$${numberToColumnName(print.repeatColumns[1])}`\n\t\t\t\t: \"\"\n\t\t]\n\t\t\t.filter(Boolean)\n\t\t\t.join(\",\");\n\t\tif (titles) {\n\t\t\tdefinitions.push(`<definedName name=\"_xlnm.Print_Titles\" localSheetId=\"${sheetIndex}\">${escapeXmlValue(titles)}</definedName>`);\n\t\t}\n\t\tif (print.printArea) {\n\t\t\tconst { firstRow, lastRow, firstColumn, lastColumn } = print.printArea;\n\t\t\tconst area = `${sheetName}!$${numberToColumnName(firstColumn)}$${firstRow}:$${numberToColumnName(lastColumn)}$${lastRow}`;\n\t\t\tdefinitions.push(`<definedName name=\"_xlnm.Print_Area\" localSheetId=\"${sheetIndex}\">${escapeXmlValue(area)}</definedName>`);\n\t\t}\n\t}\n\treturn definitions.join(\"\");\n}\n\n/** Настройки печати требуют двух согласованных OpenXML-частей: worksheet и workbook defined names. */\nfunction createMatrixPrintFeature(\n\tsheetNames: readonly string[],\n\tsheets: readonly ExcelMatrixWorkbookSheet[]\n): Feature<BrowserExcelFileContent> | undefined {\n\tif (!sheets.some(({ print }) => print)) return undefined;\n\tconst definedNames = buildDefinedNames(sheetNames, sheets);\n\treturn {\n\t\tfiles: {\n\t\t\ttransform: {\n\t\t\t\t\"xl/worksheets/sheet{id}.xml\": {\n\t\t\t\t\ttransform: (content, _options, properties) => {\n\t\t\t\t\t\tconst setup = sheets[properties.sheetIndex]?.print;\n\t\t\t\t\t\treturn setup ? applyWorksheetPrintSetup(content, setup) : content;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t\"xl/workbook.xml\": {\n\t\t\t\t\ttransform: (content) =>\n\t\t\t\t\t\tdefinedNames ? content.replace(\"</sheets>\", `</sheets><definedNames>${definedNames}</definedNames>`) : content\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\n/**\n * Формирует Blob без побочного эффекта скачивания.\n *\n * Этот вход нужен предпросмотру, file validation и сценариям, где download\n * выполняет отдельный browser-owner. KTK-specific layout сюда не передаётся.\n */\nexport async function createExcelMatrixWorkbookBlob(args: ExcelMatrixWorkbookArgs): Promise<Blob> {\n\tif (args.sheets.length === 0) throw new Error(\"Для формирования Excel-книги нужен минимум один лист\");\n\tfor (const sheet of args.sheets) {\n\t\tif (sheet.rows.length === 0) throw new Error(`Лист «${sheet.name}» не содержит строк`);\n\t\tif (sheet.print) validatePrintSetup(sheet.print);\n\t}\n\n\tconst sheetNames = resolveExcelSheetNames(args.sheets.map(({ name }) => name));\n\tconst sheets: Sheet<BrowserExcelFileContent>[] = args.sheets.map((sheet, index) => ({\n\t\tdata: sheet.rows.map((row) => [...row]),\n\t\tsheet: sheetNames[index],\n\t\tcolumns: sheet.columns?.map((column) => ({ width: column.width })),\n\t\tshowGridLines: sheet.showGridLines,\n\t\tzoomScale: sheet.zoomScale\n\t}));\n\tconst printFeature = createMatrixPrintFeature(sheetNames, args.sheets);\n\treturn writeXlsxFile(sheets, { features: printFeature ? [printFeature] : undefined }).toBlob();\n}\n\n/** Скачивает готовую матричную книгу из явного пользовательского действия. */\nexport async function downloadExcelMatrixWorkbook(fileName: string, args: ExcelMatrixWorkbookArgs): Promise<void> {\n\tconst blob = await createExcelMatrixWorkbookBlob(args);\n\tdownloadFileFromBlob(fileName, blob);\n}\n","import { DEFAULT_DATE_PRESET_NAMES, getDatePreset, type DateFormatPreset } from \"../formatters/date\";\nimport { getNumberPreset } from \"../formatters/number\";\nimport {\n\tvalidateFormattersPipelineConfig,\n\ttype FormattersPipelineConfig,\n\ttype FormattersPipelineTypedValueFormatConfig\n} from \"../formatters/pipeline\";\nimport {\n\tisODataBooleanType,\n\tisODataDateType,\n\tisODataIntegerType,\n\tisODataNumericType,\n\ttype ColumnRole,\n\ttype ODataMetaType\n} from \"../odata-service\";\n\nimport { downloadExcelFile, downloadExcelWorkbook, type ExcelCellStyle, type ExcelColumn, type ExcelRow } from \"./excel\";\n\nexport type TableExcelColumnDescriptor = {\n\tid: string;\n\theader: string;\n\twidth?: number;\n\trole?: ColumnRole;\n\todataType?: ODataMetaType;\n\tclientOnly?: boolean;\n\tformattersPipeline?: FormattersPipelineConfig;\n};\n\nexport type ResolvedExcelTable = {\n\tcolumns: ExcelColumn[];\n\trows: ExcelRow[];\n\tcellStyles?: ReadonlyArray<Readonly<Record<string, ExcelCellStyle | undefined>>>;\n};\n\nexport type DownloadResolvedExcelTableArgs = {\n\tfileName: string;\n\ttable: ResolvedExcelTable;\n\tautoFilter?: boolean;\n};\n\n/** Один подготовленный табличный лист generic Excel-книги. */\nexport type ResolvedExcelWorkbookSheet = {\n\t/** Желаемое имя листа. */\n\tname: string;\n\t/** Подготовленные колонки, строки и optional стили ячеек. */\n\ttable: ResolvedExcelTable;\n\t/** Включить автофильтр для этого листа. По умолчанию включён. */\n\tautoFilter?: boolean;\n};\n\n/** Аргументы скачивания книги из нескольких подготовленных таблиц. */\nexport type DownloadResolvedExcelWorkbookArgs = {\n\t/** Имя скачиваемого `.xlsx` файла. */\n\tfileName: string;\n\t/** Табличные листы в порядке отображения в книге. */\n\tsheets: ResolvedExcelWorkbookSheet[];\n};\n\nconst INTEGER_EXCEL_FORMAT = \"#,##0\";\nconst DEFAULT_DECIMAL_EXCEL_FORMAT = \"#,##0.0\";\nconst EXCEL_DATE_FORMAT = \"dd.mm.yyyy\";\n/**\n * Excel-маски календарных пресетов сохраняют экранную детализацию:\n * month-* выводит день и месяц без года, а month-year-* — месяц и год без дня.\n */\nconst EXCEL_DAY_MONTH_SHORT_FORMAT = \"dd.mm\";\nconst EXCEL_DAY_MONTH_MEDIUM_FORMAT = \"d mmm\";\nconst EXCEL_DAY_MONTH_LONG_FORMAT = \"d mmmm\";\nconst EXCEL_MONTH_YEAR_MEDIUM_FORMAT = \"mmm yyyy\";\nconst EXCEL_MONTH_YEAR_LONG_FORMAT = \"mmmm yyyy\";\nconst EXCEL_TIME_FORMAT = \"hh:mm\";\nconst EXCEL_TIME_SECONDS_FORMAT = \"hh:mm:ss\";\nconst EXCEL_DATETIME_FORMAT = `${EXCEL_DATE_FORMAT} ${EXCEL_TIME_FORMAT}`;\nconst EXCEL_DATETIME_SECONDS_FORMAT = `${EXCEL_DATE_FORMAT} ${EXCEL_TIME_SECONDS_FORMAT}`;\n\nfunction resolveTypedValueFormatConfig(\n\tformattersPipeline: FormattersPipelineConfig | undefined\n): FormattersPipelineTypedValueFormatConfig | undefined {\n\tconst validation = validateFormattersPipelineConfig(formattersPipeline);\n\tconst typedValueFormatStep = validation.plan?.steps.find((step) => step.type === \"typedValueFormat\");\n\n\treturn typedValueFormatStep?.type === \"typedValueFormat\" ? typedValueFormatStep.config : undefined;\n}\n\nexport function resolveTableExcelColumnType(descriptor: TableExcelColumnDescriptor): ExcelColumn[\"type\"] {\n\tif (descriptor.clientOnly) return Number;\n\tif (isODataDateType(descriptor.odataType)) return Date;\n\tif (isODataBooleanType(descriptor.odataType)) return Boolean;\n\tif (isODataNumericType(descriptor.odataType)) return Number;\n\tif (descriptor.role === \"measure\") return Number;\n\treturn String;\n}\n\nfunction resolveNumberPresetName(descriptor: TableExcelColumnDescriptor): string {\n\tconst configuredPresetName = resolveTypedValueFormatConfig(descriptor.formattersPipeline)?.numberPresetName?.trim();\n\tif (configuredPresetName && getNumberPreset(configuredPresetName)) return configuredPresetName;\n\tif (isODataIntegerType(descriptor.odataType)) return \"integer\";\n\treturn \"decimal\";\n}\n\nfunction resolveExcelNumberFormat(descriptor: TableExcelColumnDescriptor): string {\n\tconst preset = getNumberPreset(resolveNumberPresetName(descriptor));\n\tif (!preset) return DEFAULT_DECIMAL_EXCEL_FORMAT;\n\treturn preset.decimals > 0 ? `#,##0.${\"0\".repeat(preset.decimals)}` : INTEGER_EXCEL_FORMAT;\n}\n\nfunction resolveDatePresetName(descriptor: TableExcelColumnDescriptor): string {\n\tconst configuredPresetName = resolveTypedValueFormatConfig(descriptor.formattersPipeline)?.datePresetName?.trim();\n\tif (configuredPresetName) return configuredPresetName;\n\tif (descriptor.odataType === \"time\") return DEFAULT_DATE_PRESET_NAMES.time;\n\tif (descriptor.odataType === \"datetime\" || descriptor.odataType === \"datetimeOffset\") return DEFAULT_DATE_PRESET_NAMES.datetime;\n\treturn DEFAULT_DATE_PRESET_NAMES.date;\n}\n\nfunction normalizeDatePatternForExcel(pattern: string): string {\n\treturn pattern.replace(/H/g, \"h\").replace(/M/g, \"m\");\n}\n\nfunction resolveBuiltinDatePresetExcelFormat(presetName: string): string | undefined {\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.odataDatetime || presetName === DEFAULT_DATE_PRESET_NAMES.abapDatetime) {\n\t\treturn EXCEL_DATETIME_SECONDS_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.odataDate || presetName === DEFAULT_DATE_PRESET_NAMES.abapDate) {\n\t\treturn EXCEL_DATE_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.monthShort) {\n\t\treturn EXCEL_DAY_MONTH_SHORT_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.monthMedium) {\n\t\treturn EXCEL_DAY_MONTH_MEDIUM_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.monthLong) {\n\t\treturn EXCEL_DAY_MONTH_LONG_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.monthYearMedium) {\n\t\treturn EXCEL_MONTH_YEAR_MEDIUM_FORMAT;\n\t}\n\n\tif (presetName === DEFAULT_DATE_PRESET_NAMES.monthYearLong) {\n\t\treturn EXCEL_MONTH_YEAR_LONG_FORMAT;\n\t}\n\n\tif (presetName.startsWith(\"datetime\")) {\n\t\treturn presetName === DEFAULT_DATE_PRESET_NAMES.datetime || presetName === DEFAULT_DATE_PRESET_NAMES.datetimeShort\n\t\t\t? EXCEL_DATETIME_FORMAT\n\t\t\t: EXCEL_DATETIME_SECONDS_FORMAT;\n\t}\n\n\tif (presetName.startsWith(\"time\")) {\n\t\treturn presetName === DEFAULT_DATE_PRESET_NAMES.time || presetName === DEFAULT_DATE_PRESET_NAMES.timeShort\n\t\t\t? EXCEL_TIME_FORMAT\n\t\t\t: EXCEL_TIME_SECONDS_FORMAT;\n\t}\n\n\tif (presetName.startsWith(\"date\")) return EXCEL_DATE_FORMAT;\n\n\treturn undefined;\n}\n\nfunction resolveExcelDateFormatFromPreset(presetName: string, preset: DateFormatPreset | undefined): string {\n\tconst builtinFormat = resolveBuiltinDatePresetExcelFormat(presetName);\n\tif (builtinFormat) return builtinFormat;\n\tif (preset?.pattern) return normalizeDatePatternForExcel(preset.pattern);\n\n\t// Настройка datePresetName исторически хранит и ручной шаблон. Если имени\n\t// нет в реестре, передаём шаблон в Excel как есть после нормализации токенов.\n\treturn normalizeDatePatternForExcel(presetName);\n}\n\nfunction resolveExcelDateFormat(descriptor: TableExcelColumnDescriptor): string {\n\tconst presetName = resolveDatePresetName(descriptor);\n\treturn resolveExcelDateFormatFromPreset(presetName, getDatePreset(presetName));\n}\n\nfunction resolveTableExcelColumnFormat(descriptor: TableExcelColumnDescriptor, type: ExcelColumn[\"type\"]): string | undefined {\n\tif (type === Number) return resolveExcelNumberFormat(descriptor);\n\tif (type === Date) return resolveExcelDateFormat(descriptor);\n\treturn undefined;\n}\n\nexport function createTableExcelColumn(descriptor: TableExcelColumnDescriptor): ExcelColumn {\n\tconst type = resolveTableExcelColumnType(descriptor);\n\treturn {\n\t\tid: descriptor.id,\n\t\theader: descriptor.header,\n\t\twidth: descriptor.width,\n\t\ttype,\n\t\tformat: resolveTableExcelColumnFormat(descriptor, type)\n\t};\n}\n\nexport async function downloadResolvedExcelTable({ fileName, table, autoFilter = true }: DownloadResolvedExcelTableArgs): Promise<void> {\n\tawait downloadExcelFile({\n\t\tfileName,\n\t\tcolumns: table.columns,\n\t\trows: table.rows,\n\t\tautoFilter,\n\t\tgetCellStyle: ({ rowIndex, column }) => table.cellStyles?.[rowIndex]?.[column.id]\n\t});\n}\n\n/** Скачивает многолистовую книгу из таблиц, уже подготовленных доменными consumers. */\nexport async function downloadResolvedExcelWorkbook({ fileName, sheets }: DownloadResolvedExcelWorkbookArgs): Promise<void> {\n\tawait downloadExcelWorkbook({\n\t\tfileName,\n\t\tsheets: sheets.map(({ name, table, autoFilter = true }) => ({\n\t\t\tname,\n\t\t\tcolumns: table.columns,\n\t\t\trows: table.rows,\n\t\t\tautoFilter,\n\t\t\tgetCellStyle: ({ rowIndex, column }) => table.cellStyles?.[rowIndex]?.[column.id]\n\t\t}))\n\t});\n}\n"],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import { type Cell } from "write-excel-file/browser";
2
+ /** Одна строка произвольной Excel-матрицы. Объединения задаются стилями первой ячейки диапазона. */
3
+ export type ExcelMatrixRow = readonly Cell[];
4
+ /** Поля задаются в дюймах, как в OpenXML и диалоге параметров страницы Excel. */
5
+ export type ExcelPrintMargins = {
6
+ readonly left: number;
7
+ readonly right: number;
8
+ readonly top: number;
9
+ readonly bottom: number;
10
+ readonly header: number;
11
+ readonly footer: number;
12
+ };
13
+ /** Видимый колонтитул не принимает raw Excel-коды, чтобы текст не мог случайно изменить разметку секций. */
14
+ export type ExcelPrintHeaderFooter = {
15
+ readonly left?: string;
16
+ readonly center?: string;
17
+ readonly right?: string;
18
+ readonly includePageNumber?: boolean;
19
+ };
20
+ /** One-based прямоугольник печати соответствует координатам строк и столбцов Excel. */
21
+ export type ExcelPrintArea = {
22
+ readonly firstRow: number;
23
+ readonly lastRow: number;
24
+ readonly firstColumn: number;
25
+ readonly lastColumn: number;
26
+ };
27
+ /**
28
+ * Печатный контракт одного листа.
29
+ *
30
+ * `columnBreaksAfter` и `rowBreaksAfter` используют one-based координату
31
+ * последней ячейки страницы. Явная модель не позволяет consumer перепутать
32
+ * индекс массива с адресом Excel и разрезать составной блок на печати.
33
+ */
34
+ export type ExcelPrintSetup = {
35
+ readonly orientation: "portrait" | "landscape";
36
+ readonly paperSize?: "a4" | "a3";
37
+ readonly fitToWidthPages?: number;
38
+ readonly fitToHeightPages?: number;
39
+ readonly pageOrder?: "downThenOver" | "overThenDown";
40
+ readonly margins?: ExcelPrintMargins;
41
+ readonly repeatRows?: readonly [firstRow: number, lastRow: number];
42
+ readonly repeatColumns?: readonly [firstColumn: number, lastColumn: number];
43
+ readonly printArea?: ExcelPrintArea;
44
+ readonly columnBreaksAfter?: readonly number[];
45
+ readonly rowBreaksAfter?: readonly number[];
46
+ readonly header?: ExcelPrintHeaderFooter;
47
+ readonly footer?: ExcelPrintHeaderFooter;
48
+ };
49
+ /** Произвольный лист сохраняет готовую матрицу и не навязывает табличную строку заголовков. */
50
+ export type ExcelMatrixWorkbookSheet = {
51
+ readonly name: string;
52
+ readonly rows: readonly ExcelMatrixRow[];
53
+ readonly columns?: readonly {
54
+ readonly width?: number;
55
+ }[];
56
+ readonly showGridLines?: boolean;
57
+ readonly zoomScale?: number;
58
+ readonly print?: ExcelPrintSetup;
59
+ };
60
+ /** Аргументы generic-книги, пригодной как для скачивания, так и для file validation. */
61
+ export type ExcelMatrixWorkbookArgs = {
62
+ readonly sheets: readonly ExcelMatrixWorkbookSheet[];
63
+ };
64
+ /**
65
+ * Формирует Blob без побочного эффекта скачивания.
66
+ *
67
+ * Этот вход нужен предпросмотру, file validation и сценариям, где download
68
+ * выполняет отдельный browser-owner. KTK-specific layout сюда не передаётся.
69
+ */
70
+ export declare function createExcelMatrixWorkbookBlob(args: ExcelMatrixWorkbookArgs): Promise<Blob>;
71
+ /** Скачивает готовую матричную книгу из явного пользовательского действия. */
72
+ export declare function downloadExcelMatrixWorkbook(fileName: string, args: ExcelMatrixWorkbookArgs): Promise<void>;
73
+ //# sourceMappingURL=matrixWorkbook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matrixWorkbook.d.ts","sourceRoot":"","sources":["../../src/excel/matrixWorkbook.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  export * from "./broadcast";
2
+ export * from "./invalidationBroadcast";
2
3
  export * from "./persistence";
3
4
  export * from "./queryMeta";
4
5
  export * from "./queryPersistenceAdapter";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/query-client/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/query-client/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,yBAAyB,CAAC;AACxC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC"}
@@ -69,43 +69,83 @@ function f(t) {
69
69
  return e(t) ? t.type === "invalidate" ? Array.isArray(t.keys) ? t.refetchType === void 0 || t.refetchType === "none" || t.refetchType === "active" || t.refetchType === "inactive" || t.refetchType === "all" : !1 : t.type === "setQueryData" && "key" in t && "data" in t : !1;
70
70
  }
71
71
  //#endregion
72
+ //#region src/query-client/invalidationBroadcast.ts
73
+ function p(t) {
74
+ return e(t) && t.type === "query.invalidate" && typeof t.senderId == "string" && Array.isArray(t.queryKey);
75
+ }
76
+ function m(e, n) {
77
+ if (typeof window > "u" || typeof BroadcastChannel > "u") return {
78
+ broadcast: () => void 0,
79
+ cleanup: () => void 0
80
+ };
81
+ let r = t(), i = new BroadcastChannel(n.channelName), a = !1, o = !1, s = (e) => {
82
+ o || !n.matchesQuery(e) || i.postMessage({
83
+ type: "query.invalidate",
84
+ senderId: r,
85
+ queryKey: e
86
+ });
87
+ }, c = e.getQueryCache().subscribe((e) => {
88
+ a || e.type !== "updated" || e.action.type !== "invalidate" || s(e.query.queryKey);
89
+ });
90
+ return i.onmessage = (t) => {
91
+ let i = t.data;
92
+ if (!(o || !p(i) || i.senderId === r || !n.matchesQuery(i.queryKey))) {
93
+ a = !0;
94
+ try {
95
+ e.invalidateQueries({
96
+ queryKey: i.queryKey,
97
+ exact: !0,
98
+ refetchType: n.refetchType ?? "active"
99
+ });
100
+ } finally {
101
+ a = !1;
102
+ }
103
+ }
104
+ }, {
105
+ broadcast: s,
106
+ cleanup() {
107
+ o || (o = !0, c(), i.close());
108
+ }
109
+ };
110
+ }
111
+ //#endregion
72
112
  //#region src/query-client/queryPersistenceAdapter.ts
73
- var p = {};
74
- function m(e) {
75
- p = e;
113
+ var h = {};
114
+ function g(e) {
115
+ h = e;
76
116
  }
77
- function h() {
78
- return p;
117
+ function _() {
118
+ return h;
79
119
  }
80
120
  //#endregion
81
121
  //#region src/query-client/persistence.ts
82
- var g = "queries", _ = __REACT_QUERY_PERSISTENCE_BUSTER__, v = 7776e6;
83
- function y(e) {
122
+ var v = "queries", y = __REACT_QUERY_PERSISTENCE_BUSTER__, b = 7776e6;
123
+ function x(e) {
84
124
  if (e) return e;
85
125
  if (globalThis.indexedDB !== void 0) return globalThis.indexedDB;
86
126
  }
87
- function b(e) {
127
+ function S(e) {
88
128
  return new Promise((t, n) => {
89
129
  e.onsuccess = () => t(e.result), e.onerror = () => n(e.error);
90
130
  });
91
131
  }
92
- function x() {
132
+ function C() {
93
133
  if (__DEV__) return `dev:${__APP_ID__}`;
94
- let e = h().resolveSystemIdentifier?.();
134
+ let e = _().resolveSystemIdentifier?.();
95
135
  if (e) return e;
96
136
  let t = typeof window > "u" ? "server" : window.location.hostname;
97
137
  return `${__APP_ID__}:${t}`;
98
138
  }
99
- function S() {
100
- return `${x()}`;
139
+ function w() {
140
+ return `${C()}`;
101
141
  }
102
- function C(e) {
142
+ function T(e) {
103
143
  return e.meta?.persist === !0 && e.meta.sessionScoped !== !0;
104
144
  }
105
- function w(e = {}) {
106
- let t = y(e.indexedDB);
145
+ function E(e = {}) {
146
+ let t = x(e.indexedDB);
107
147
  if (!t) return;
108
- let n = t, r = e.dbName ?? x(), i = e.storeName ?? g, a = null;
148
+ let n = t, r = e.dbName ?? C(), i = e.storeName ?? v, a = null;
109
149
  function o() {
110
150
  return a || (a = new Promise((e, t) => {
111
151
  let a = n.open(r, 1);
@@ -116,7 +156,7 @@ function w(e = {}) {
116
156
  }), a);
117
157
  }
118
158
  async function s(e, t) {
119
- return b(t((await o()).transaction(i, e).objectStore(i)));
159
+ return S(t((await o()).transaction(i, e).objectStore(i)));
120
160
  }
121
161
  async function c(e, t) {
122
162
  let n = await o();
@@ -165,26 +205,26 @@ function w(e = {}) {
165
205
  }
166
206
  };
167
207
  }
168
- function T() {
169
- let e = w();
208
+ function D() {
209
+ let e = E();
170
210
  if (e) return r({
171
211
  storage: e,
172
212
  serialize: (e) => e,
173
213
  deserialize: (e) => e,
174
- prefix: S(),
175
- buster: h().persistenceBuster ?? _,
176
- maxAge: v,
214
+ prefix: w(),
215
+ buster: _().persistenceBuster ?? y,
216
+ maxAge: b,
177
217
  refetchOnRestore: !0,
178
- filters: { predicate: C }
218
+ filters: { predicate: T }
179
219
  });
180
220
  }
181
221
  //#endregion
182
222
  //#region src/query-client/queryMeta.ts
183
- var E = { persist: !0 }, D = { sessionScoped: !0 };
223
+ var O = { persist: !0 }, k = { sessionScoped: !0 };
184
224
  //#endregion
185
225
  //#region src/query-client/queryClient.ts
186
- function O({ onQueryError: e, onMutationError: t }) {
187
- let n = T();
226
+ function A({ onQueryError: e, onMutationError: t }) {
227
+ let n = D();
188
228
  return new o({
189
229
  queryCache: new a({ onError: e }),
190
230
  mutationCache: new i({ onError: t }),
@@ -203,35 +243,35 @@ function O({ onQueryError: e, onMutationError: t }) {
203
243
  }
204
244
  });
205
245
  }
206
- async function k(e, t, n) {
246
+ async function j(e, t, n) {
207
247
  await e.cancelQueries({ queryKey: t });
208
248
  let r = e.getQueryData(t);
209
249
  return e.setQueryData(t, n), { previous: r };
210
250
  }
211
- function A(e, t, n) {
251
+ function M(e, t, n) {
212
252
  n?.previous !== void 0 && e.setQueryData(t, n.previous);
213
253
  }
214
- function j(e, t, n, r) {
254
+ function N(e, t, n, r) {
215
255
  e.setQueryData(t, (e) => r(e, n));
216
256
  }
217
- async function M(e, t, n) {
218
- let r = n?.refetchType ?? "none", i = N(t) ? t : [t];
257
+ async function P(e, t, n) {
258
+ let r = n?.refetchType ?? "none", i = F(t) ? t : [t];
219
259
  await Promise.all(i.map((t) => e.invalidateQueries({
220
260
  queryKey: t,
221
261
  refetchType: r
222
262
  })));
223
263
  }
224
- function N(e) {
264
+ function F(e) {
225
265
  return Array.isArray(e) ? Array.isArray(e[0]) : !1;
226
266
  }
227
267
  //#endregion
228
268
  //#region src/query-client/sessionScoped.ts
229
- var P = "session-scoped-query-cache-reset", F;
230
- function I(e) {
269
+ var I = "session-scoped-query-cache-reset", L;
270
+ function R(e) {
231
271
  return e.meta?.sessionScoped === !0;
232
272
  }
233
- async function L(e, t = {}) {
234
- let n = { predicate: I };
273
+ async function z(e, t = {}) {
274
+ let n = { predicate: R };
235
275
  await e.cancelQueries(n);
236
276
  let r = e.getQueryCache().findAll(n);
237
277
  for (let t of r) {
@@ -245,26 +285,26 @@ async function L(e, t = {}) {
245
285
  });
246
286
  }
247
287
  await e.refetchQueries({
248
- predicate: (e) => I(e) && e.getObserversCount() > 0,
288
+ predicate: (e) => R(e) && e.getObserversCount() > 0,
249
289
  type: "active"
250
- }, { cancelRefetch: !0 }), t.broadcast !== !1 && F?.();
290
+ }, { cancelRefetch: !0 }), t.broadcast !== !1 && L?.();
251
291
  }
252
- function R(e, t) {
292
+ function B(e, t) {
253
293
  if (typeof window > "u" || typeof BroadcastChannel > "u") return { cleanup: () => void 0 };
254
294
  let n = new BroadcastChannel(t.channelName), r = () => {
255
- let e = { type: P };
295
+ let e = { type: I };
256
296
  n.postMessage(e);
257
297
  };
258
- return F = r, n.onmessage = (t) => {
259
- z(t.data) && L(e, { broadcast: !1 });
298
+ return L = r, n.onmessage = (t) => {
299
+ V(t.data) && z(e, { broadcast: !1 });
260
300
  }, { cleanup: () => {
261
- n.onmessage = null, n.close(), F === r && (F = void 0);
301
+ n.onmessage = null, n.close(), L === r && (L = void 0);
262
302
  } };
263
303
  }
264
- function z(e) {
265
- return typeof e == "object" && !!e && "type" in e && e.type === P;
304
+ function V(e) {
305
+ return typeof e == "object" && !!e && "type" in e && e.type === I;
266
306
  }
267
307
  //#endregion
268
- export { _ as REACT_QUERY_PERSISTENCE_BUSTER, v as REACT_QUERY_PERSISTENCE_MAX_AGE, l as broadcastCacheEvent, m as configureQueryPersistenceProjectAdapter, w as createIndexedDbQueryStorage, O as createQueryClient, T as createReactQueryPersister, h as getQueryPersistenceProjectAdapter, u as installReactQueryBroadcast, R as installSessionScopedQueryReset, I as isSessionScopedQuery, A as onErrorOptimistic, k as onMutateOptimistic, M as onSettledOptimistic, j as onSuccessOptimistic, E as persistedQueryMeta, L as resetSessionScopedQueries, D as sessionScopedQueryMeta, c as setBroadcastFn, C as shouldPersistQuery };
308
+ export { y as REACT_QUERY_PERSISTENCE_BUSTER, b as REACT_QUERY_PERSISTENCE_MAX_AGE, l as broadcastCacheEvent, g as configureQueryPersistenceProjectAdapter, E as createIndexedDbQueryStorage, A as createQueryClient, D as createReactQueryPersister, _ as getQueryPersistenceProjectAdapter, m as installQueryInvalidationBroadcast, u as installReactQueryBroadcast, B as installSessionScopedQueryReset, R as isSessionScopedQuery, M as onErrorOptimistic, j as onMutateOptimistic, P as onSettledOptimistic, N as onSuccessOptimistic, O as persistedQueryMeta, z as resetSessionScopedQueries, k as sessionScopedQueryMeta, c as setBroadcastFn, T as shouldPersistQuery };
269
309
 
270
310
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/query-client/broadcast.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","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 } from \"@tanstack/react-query\";\n\nimport { getQueryPersistenceProjectAdapter } from \"./queryPersistenceAdapter\";\n\nconst STORE_NAME = \"queries\";\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\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/**\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\nexport function shouldPersistQuery(query: Pick<Query, \"meta\">) {\n\treturn query.meta?.persist === true && query.meta.sessionScoped !== true;\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\n\treturn experimental_createQueryPersister<PersistedQuery>({\n\t\tstorage,\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}\n","import { type QueryMeta } from \"@tanstack/react-query\";\n\n/**\n * Общие признаки query, которые управляют инфраструктурой клиентского кеша.\n *\n * `persist` разрешает долговременное хранение публичных справочных данных,\n * а `sessionScoped` помечает данные, доступность или содержимое которых зависит\n * от текущей серверной сессии. Эти признаки нельзя совмещать: session-scoped\n * query никогда не должна переживать смену пользователя или роли.\n */\nexport type AppQueryMeta = {\n\treadonly persist?: boolean;\n\treadonly sessionScoped?: boolean;\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","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 } from \"@tanstack/react-query\";\n\nimport { getQueryPersistenceProjectAdapter } from \"./queryPersistenceAdapter\";\n\nconst STORE_NAME = \"queries\";\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\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/**\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\nexport function shouldPersistQuery(query: Pick<Query, \"meta\">) {\n\treturn query.meta?.persist === true && query.meta.sessionScoped !== true;\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\n\treturn experimental_createQueryPersister<PersistedQuery>({\n\t\tstorage,\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}\n","import { type QueryMeta } from \"@tanstack/react-query\";\n\n/**\n * Общие признаки query, которые управляют инфраструктурой клиентского кеша.\n *\n * `persist` разрешает долговременное хранение публичных справочных данных,\n * а `sessionScoped` помечает данные, доступность или содержимое которых зависит\n * от текущей серверной сессии. Эти признаки нельзя совмещать: session-scoped\n * query никогда не должна переживать смену пользователя или роли.\n */\nexport type AppQueryMeta = {\n\treadonly persist?: boolean;\n\treadonly sessionScoped?: boolean;\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","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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"}
@@ -0,0 +1,20 @@
1
+ import type { QueryClient, QueryKey } from "@tanstack/react-query";
2
+ export type QueryInvalidationBroadcastOptions = {
3
+ /** Уникальное в пределах origin имя канала приложения. */
4
+ readonly channelName: string;
5
+ /** Ограничивает как исходящие, так и входящие Query keys. */
6
+ readonly matchesQuery: (queryKey: QueryKey) => boolean;
7
+ /** Определяет, какие queries получающей вкладки будут перезапрошены сразу. */
8
+ readonly refetchType?: "none" | "active" | "inactive" | "all";
9
+ };
10
+ export type InstalledQueryInvalidationBroadcast = {
11
+ /** Явно передаёт точечную инвалидацию без изменения локального QueryClient. */
12
+ readonly broadcast: (queryKey: QueryKey) => void;
13
+ readonly cleanup: () => void;
14
+ };
15
+ /**
16
+ * Передаёт между вкладками только факт инвалидации выбранного Query key.
17
+ * Server data остаётся у QueryClient каждой вкладки и заново читается её queryFn.
18
+ */
19
+ export declare function installQueryInvalidationBroadcast(queryClient: QueryClient, options: QueryInvalidationBroadcastOptions): InstalledQueryInvalidationBroadcast;
20
+ //# sourceMappingURL=invalidationBroadcast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invalidationBroadcast.d.ts","sourceRoot":"","sources":["../../src/query-client/invalidationBroadcast.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEnE,MAAM,MAAM,iCAAiC,GAAG;IAC/C,0DAA0D;IAC1D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,YAAY,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC;IACvD,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,KAAK,CAAC;CAC9D,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG;IACjD,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;IACjD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC7B,CAAC;AAYF;;;GAGG;AACH,wBAAgB,iCAAiC,CAChD,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,iCAAiC,GACxC,mCAAmC,CA8CrC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryuzaki13/react-foundation-lib",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "Reusable React and TypeScript foundation utilities, hooks, and primitives.",
5
5
  "license": "MIT",
6
6
  "author": "Ryuzaki13",
@@ -241,6 +241,48 @@ await downloadExcelWorkbook({
241
241
 
242
242
  `fileName` передаётся browser writer. Рекомендуется явно добавлять расширение `.xlsx` и заранее очищать пользовательский ввод от нежелательных символов имени файла.
243
243
 
244
+ ## Произвольная матрица и параметры печати
245
+
246
+ Для расписаний, печатных форм и других документов без обычной строки колонок
247
+ используйте `createExcelMatrixWorkbookBlob` или `downloadExcelMatrixWorkbook`.
248
+ Доменный consumer передаёт уже готовые строки и стили ячеек, а foundation API
249
+ отвечает за имена листов, объединения, OpenXML print area, повтор заголовков и
250
+ ручные разрывы страниц.
251
+
252
+ ```ts
253
+ import { downloadExcelMatrixWorkbook } from "@ryuzaki13/react-foundation-lib/excel";
254
+
255
+ await downloadExcelMatrixWorkbook("Расписание.xlsx", {
256
+ sheets: [
257
+ {
258
+ name: "Неделя",
259
+ rows: [[{ value: "Расписание", type: String, columnSpan: 2 }, null]],
260
+ columns: [{ width: 8 }, { width: 24 }],
261
+ showGridLines: false,
262
+ print: {
263
+ orientation: "portrait",
264
+ paperSize: "a4",
265
+ fitToHeightPages: 1,
266
+ repeatRows: [1, 1],
267
+ columnBreaksAfter: [12],
268
+ printArea: { firstRow: 1, lastRow: 20, firstColumn: 1, lastColumn: 24 },
269
+ footer: { left: "Отчёт 7c9134", includePageNumber: true }
270
+ }
271
+ }
272
+ ]
273
+ });
274
+ ```
275
+
276
+ `columnBreaksAfter` и `rowBreaksAfter` используют one-based номер последней
277
+ колонки или строки страницы. Например, `columnBreaksAfter: [12]` переносит
278
+ колонку `M` на следующую страницу. `fitToHeightPages` и `fitToWidthPages` должны
279
+ быть положительными: отсутствие свойства означает автоматическое число страниц
280
+ по этой оси.
281
+
282
+ `createExcelMatrixWorkbookBlob` не инициирует скачивание и подходит для preview
283
+ и проверки сформированного файла. `downloadExcelMatrixWorkbook` делегирует
284
+ browser download общему `downloadFileFromBlob`.
285
+
244
286
  ## Имена листов
245
287
 
246
288
  ### `resolveExcelSheetNames(names)`