@ryuzaki13/react-foundation-lib 1.4.0 → 1.4.2

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.
Files changed (101) hide show
  1. package/README.md +172 -114
  2. package/dist/chunks/{formatters-CBgrBeSA.js → formatters-D33Uh3si.js} +7 -8
  3. package/dist/chunks/{formatters-CBgrBeSA.js.map → formatters-D33Uh3si.js.map} +1 -1
  4. package/dist/chunks/odata-service-CWkHASdw.js +2211 -0
  5. package/dist/chunks/odata-service-CWkHASdw.js.map +1 -0
  6. package/dist/chunks/{validate-BW-7BZy_.js → validate-DyzojM0w.js} +2 -2
  7. package/dist/chunks/{validate-BW-7BZy_.js.map → validate-DyzojM0w.js.map} +1 -1
  8. package/dist/error-report/index.js +2 -2
  9. package/dist/excel/index.js +20 -21
  10. package/dist/excel/index.js.map +1 -1
  11. package/dist/formatters/index.js +4 -5
  12. package/dist/{odata → odata-service}/buildSeparatedArrays.d.ts +1 -1
  13. package/dist/odata-service/buildSeparatedArrays.d.ts.map +1 -0
  14. package/dist/{odata/types.d.ts → odata-service/collectionTypes.d.ts} +1 -1
  15. package/dist/odata-service/collectionTypes.d.ts.map +1 -0
  16. package/dist/{odata → odata-service}/dateUtils.d.ts +1 -1
  17. package/dist/odata-service/dateUtils.d.ts.map +1 -0
  18. package/dist/{odata → odata-service}/findCollectionPairs.d.ts +1 -1
  19. package/dist/odata-service/findCollectionPairs.d.ts.map +1 -0
  20. package/dist/odata-service/index.d.ts +5 -0
  21. package/dist/odata-service/index.d.ts.map +1 -1
  22. package/dist/odata-service/index.js +2 -2
  23. package/dist/{odata/store.d.ts → odata-service/odataCollectionStore.d.ts} +1 -1
  24. package/dist/odata-service/odataCollectionStore.d.ts.map +1 -0
  25. package/dist/query-client/index.js.map +1 -1
  26. package/dist/query-client/queryClient.d.ts +1 -3
  27. package/dist/query-client/queryClient.d.ts.map +1 -1
  28. package/package.json +1 -5
  29. package/src/array/README.mdx +1630 -0
  30. package/src/binary/README.mdx +1405 -0
  31. package/src/bounded-copy-stack/README.mdx +257 -0
  32. package/src/context-menu/README.mdx +326 -0
  33. package/src/copy/README.mdx +992 -0
  34. package/src/crypto/README.mdx +1052 -0
  35. package/src/currency/README.mdx +155 -0
  36. package/src/date-segments/README.mdx +319 -0
  37. package/src/dom/README.mdx +2179 -0
  38. package/src/error/README.mdx +95 -0
  39. package/src/error-report/README.mdx +612 -0
  40. package/src/excel/README.mdx +449 -0
  41. package/src/file/README.mdx +1957 -0
  42. package/src/fingerprint/README.mdx +100 -0
  43. package/src/form/README.mdx +101 -0
  44. package/src/formatters/README.mdx +469 -0
  45. package/src/formatters/boolean/README.mdx +494 -0
  46. package/src/formatters/date/README.mdx +732 -0
  47. package/src/formatters/number/README.mdx +542 -0
  48. package/src/formatters/pipeline/README.mdx +629 -0
  49. package/src/formatters/rowBased/README.mdx +486 -0
  50. package/src/formatters/strings/README.mdx +442 -0
  51. package/src/formatters/valueState/README.mdx +459 -0
  52. package/src/formulas/README.mdx +1283 -0
  53. package/src/foundationLib.mdx +70 -54
  54. package/src/hooks/README.mdx +988 -0
  55. package/src/http/README.mdx +126 -0
  56. package/src/media/README.mdx +1853 -0
  57. package/src/notifications/README.mdx +323 -0
  58. package/src/number-scale/README.mdx +318 -0
  59. package/src/odata-service/README.mdx +2916 -0
  60. package/src/presets/README.mdx +136 -0
  61. package/src/pwa/README.mdx +394 -0
  62. package/src/query-client/README.mdx +2500 -0
  63. package/src/range-output/README.mdx +161 -0
  64. package/src/seo/README.mdx +279 -0
  65. package/src/session-storage/README.mdx +125 -0
  66. package/src/string-comparison/README.mdx +96 -0
  67. package/src/table/README.mdx +3106 -0
  68. package/src/three-scene/README.mdx +248 -0
  69. package/src/types/README.mdx +172 -0
  70. package/src/utils/README.mdx +1724 -0
  71. package/src/validators/README.mdx +239 -0
  72. package/src/virtualizer/README.mdx +138 -0
  73. package/src/xml/README.mdx +149 -0
  74. package/dist/chunks/dateUtils-B5V5aXB-.js +0 -847
  75. package/dist/chunks/dateUtils-B5V5aXB-.js.map +0 -1
  76. package/dist/chunks/odata-service-B6TxBSLm.js +0 -1277
  77. package/dist/chunks/odata-service-B6TxBSLm.js.map +0 -1
  78. package/dist/odata/buildSeparatedArrays.d.ts.map +0 -1
  79. package/dist/odata/dateUtils.d.ts.map +0 -1
  80. package/dist/odata/findCollectionPairs.d.ts.map +0 -1
  81. package/dist/odata/index.d.ts +0 -6
  82. package/dist/odata/index.d.ts.map +0 -1
  83. package/dist/odata/index.js +0 -102
  84. package/dist/odata/index.js.map +0 -1
  85. package/dist/odata/store.d.ts.map +0 -1
  86. package/dist/odata/types.d.ts.map +0 -1
  87. package/src/binary/README.md +0 -110
  88. package/src/docs/browserAndReact.mdx +0 -150
  89. package/src/docs/filesAndExcel.mdx +0 -163
  90. package/src/docs/formatters-runtime.mdx +0 -455
  91. package/src/docs/formatters.mdx +0 -380
  92. package/src/docs/odataAndTables.mdx +0 -178
  93. package/src/docs/pureUtilities.mdx +0 -96
  94. package/src/docs/runtimeAndState.mdx +0 -155
  95. package/src/file/README.md +0 -289
  96. package/src/formatters/date/README.md +0 -520
  97. package/src/formatters/number/README.md +0 -123
  98. package/src/formatters/valueState/README.md +0 -275
  99. package/src/formulas/README.md +0 -42
  100. package/src/pwa/README.md +0 -638
  101. package/src/query-client/README.md +0 -565
@@ -0,0 +1,239 @@
1
+ import { Meta } from "@storybook/addon-docs/blocks";
2
+
3
+ <Meta title="Foundation Lib/Validation/Runtime Guards" />
4
+
5
+ # Runtime guards и validators через `@ryuzaki13/react-foundation-lib/validators`
6
+
7
+ Модуль содержит небольшие проверки для `unknown`, object-like значений, image extensions, positive integer, URL и Floating UI DOM reference.
8
+
9
+ > Название функции не расширяет её контракт. Например, `isSafe` проверяет только отсутствие `null`/`undefined`, а `isRecord` допускает `Date` и `Map`. Ниже указано точное поведение каждой проверки.
10
+
11
+ ## Содержание
12
+
13
+ - [Установка и импорт](#установка-и-импорт)
14
+ - [Как работают type guards](#как-работают-type-guards)
15
+ - [`isSafe`](#issafe)
16
+ - [`isObject`, `isRecord`, `asRecord`](#isobject-isrecord-asrecord)
17
+ - [`isPlainObject`](#isplainobject)
18
+ - [`readPositiveInteger`](#readpositiveinteger)
19
+ - [Image extensions](#image-extensions)
20
+ - [`isDomReference`](#isdomreference)
21
+ - [`validateUrl`](#validateurl)
22
+ - [Security boundary](#security-boundary)
23
+ - [Полный API](#полный-api)
24
+
25
+ ## Установка и импорт
26
+
27
+ ```ts
28
+ import {
29
+ asRecord,
30
+ IMAGE_EXTENSIONS,
31
+ isDomReference,
32
+ isImageExtension,
33
+ isObject,
34
+ isPlainObject,
35
+ isRecord,
36
+ isSafe,
37
+ readPositiveInteger,
38
+ validateUrl
39
+ } from "@ryuzaki13/react-foundation-lib/validators";
40
+ ```
41
+
42
+ `isDomReference` использует type из `@floating-ui/react`; host entrypoint должен разрешить совместимую peer dependency. Остальные функции не требуют React.
43
+
44
+ ## Как работают type guards
45
+
46
+ Type guard возвращает boolean и помогает TypeScript сузить значение:
47
+
48
+ ```ts
49
+ function readCode(value: unknown): string | undefined {
50
+ if (!isRecord(value)) return undefined;
51
+ return typeof value.code === "string" ? value.code : undefined;
52
+ }
53
+ ```
54
+
55
+ Guard не преобразует значение и не гарантирует поля, которые сам не проверил.
56
+
57
+ ## `isSafe`
58
+
59
+ ```ts
60
+ function isSafe<T>(value: T | null | undefined): value is T;
61
+ ```
62
+
63
+ Возвращает `false` только для `null` и `undefined`:
64
+
65
+ ```ts
66
+ [0, "", false, null, undefined, "ok"].filter(isSafe);
67
+ // [0, "", false, "ok"].
68
+ ```
69
+
70
+ `0`, пустая строка и `false` являются безопасными в смысле этой функции. `isSafe` не проверяет XSS, SQL, URL, число, object schema или authorization.
71
+
72
+ ## `isObject`, `isRecord`, `asRecord`
73
+
74
+ Три функции используют одинаковую базовую проверку: значение truthy, `typeof === "object"`, не array.
75
+
76
+ | Вход | `isObject` / `isRecord` | `asRecord` |
77
+ | ------------ | :---------------------: | -------------------------------------- |
78
+ | `{}` | `true` | тот же object |
79
+ | `new Date()` | `true` | тот же Date, типизированный как record |
80
+ | `new Map()` | `true` | тот же Map |
81
+ | DOM element | `true` | тот же element |
82
+ | `[]` | `false` | `null` |
83
+ | `null` | `false` | `null` |
84
+ | function | `false` | `null` |
85
+
86
+ `isObject` сужает до `object`, `isRecord` — до `Record<string, unknown>`, `asRecord` возвращает record или `null`.
87
+
88
+ Они не проверяют plain object и не гарантируют JSON-compatible данные.
89
+
90
+ ## `isPlainObject`
91
+
92
+ Возвращает `true`, только если prototype равен `Object.prototype` или `null`:
93
+
94
+ ```ts
95
+ isPlainObject({}); // true.
96
+ isPlainObject(Object.create(null)); // true.
97
+ isPlainObject(new Date()); // false.
98
+ isPlainObject(new Map()); // false.
99
+ ```
100
+
101
+ Сигнатура принимает `object`, поэтому сначала сузьте `unknown`:
102
+
103
+ ```ts
104
+ if (isObject(value) && isPlainObject(value)) {
105
+ // Plain record-like object.
106
+ }
107
+ ```
108
+
109
+ Прямой runtime-вызов `isPlainObject(null as never)` приведёт к ошибке `Object.getPrototypeOf`. TypeScript contract запрещает такой аргумент.
110
+
111
+ ## `readPositiveInteger`
112
+
113
+ ```ts
114
+ readPositiveInteger(12); // 12.
115
+ readPositiveInteger("12"); // 12.
116
+ readPositiveInteger(" 12 "); // 12.
117
+ readPositiveInteger(0); // undefined.
118
+ readPositiveInteger(-1); // undefined.
119
+ readPositiveInteger(1.5); // undefined.
120
+ readPositiveInteger("bad"); // undefined.
121
+ ```
122
+
123
+ String преобразуется стандартным `Number(value)`, затем проверяются:
124
+
125
+ - runtime type number;
126
+ - `Number.isInteger`;
127
+ - значение строго больше `0`.
128
+
129
+ Следствия стандартного `Number`:
130
+
131
+ ```ts
132
+ readPositiveInteger("1e2"); // 100.
133
+ readPositiveInteger("0x10"); // 16.
134
+ readPositiveInteger(""); // undefined, потому что Number("") = 0.
135
+ ```
136
+
137
+ Если формат input должен быть только decimal digits, дополнительно проверьте строковую грамматику на своей boundary.
138
+
139
+ ## Image extensions
140
+
141
+ ```ts
142
+ isImageExtension("PNG"); // true.
143
+ isImageExtension("svg"); // true.
144
+ isImageExtension("pdf"); // false.
145
+ ```
146
+
147
+ Список:
148
+
149
+ ```ts
150
+ ["png", "jpg", "jpeg", "gif", "webp", "svg"];
151
+ ```
152
+
153
+ Проверка регистронезависима через `toLowerCase`, но не делает trim и ожидает расширение без точки:
154
+
155
+ ```ts
156
+ isImageExtension(".png"); // false.
157
+ isImageExtension(" png "); // false.
158
+ ```
159
+
160
+ `IMAGE_EXTENSIONS` экспортирован как изменяемый array по текущему типу. Рассматривайте его как библиотечную константу и не вызывайте `push/splice`.
161
+
162
+ Расширение имени не доказывает MIME/type и безопасность содержимого. Для file/image boundary используйте `/file` и server-side validation.
163
+
164
+ ## `isDomReference`
165
+
166
+ ```ts
167
+ if (isDomReference(floatingReference)) {
168
+ floatingReference.focus();
169
+ }
170
+ ```
171
+
172
+ Возвращает `true`, только если reference является browser `Element`. Virtual element Floating UI и `null` возвращают `false`.
173
+
174
+ Функция выполняет `ref instanceof Element`. В server runtime без global `Element` её вызов может привести к `ReferenceError`; вызывайте только в browser boundary или после проверки DOM environment.
175
+
176
+ ## `validateUrl`
177
+
178
+ ```ts
179
+ const result = validateUrl("https://example.com/path");
180
+
181
+ if (!result.isValid) {
182
+ showError(result.error);
183
+ }
184
+ ```
185
+
186
+ Возможные результаты:
187
+
188
+ ```ts
189
+ { isValid: true, error: "" }
190
+ { isValid: false, error: "URL не может быть пустым" }
191
+ { isValid: false, error: "URL слишком длинный" }
192
+ { isValid: false, error: "URL содержит недопустимые символы" }
193
+ { isValid: false, error: "Некорректный формат URL" }
194
+ ```
195
+
196
+ Алгоритм:
197
+
198
+ 1. Отклоняет пустую строку.
199
+ 2. Если строка не начинается с lowercase `http`, временно добавляет `https://` для `new URL`.
200
+ 3. Проверяет длину исходной строки не больше `2048`.
201
+ 4. Применяет консервативное регулярное выражение.
202
+
203
+ Поддерживаемая грамматика намеренно узкая:
204
+
205
+ - optional `http://` или `https://`;
206
+ - domain с точкой;
207
+ - lowercase TLD длиной 2–6 по regex;
208
+ - простой path из ограниченного набора символов.
209
+
210
+ Query string, hash, port, localhost, IP-адреса, internationalized domains, многие валидные Unicode/path characters и длинные современные TLD могут быть отклонены.
211
+
212
+ ## Security boundary
213
+
214
+ `validateUrl` — UI-проверка формата, не security policy. Она не:
215
+
216
+ - проверяет allowlist host/protocol;
217
+ - делает DNS resolution;
218
+ - защищает backend от SSRF;
219
+ - гарантирует отсутствие redirect на опасный host;
220
+ - проверяет доступность ресурса;
221
+ - санитизирует HTML;
222
+ - подтверждает, что URL ведёт на изображение.
223
+
224
+ Для внешней навигации/запроса применяйте отдельную security policy владельца boundary.
225
+
226
+ ## Полный API
227
+
228
+ | Export | Точное назначение |
229
+ | --------------------- | --------------------------------------- |
230
+ | `isSafe` | Исключает `null`/`undefined` |
231
+ | `isObject` | Non-array truthy object |
232
+ | `isRecord` | Та же runtime-проверка с record type |
233
+ | `asRecord` | Record или `null` |
234
+ | `isPlainObject` | Проверяет prototype |
235
+ | `readPositiveInteger` | Positive integer number или `undefined` |
236
+ | `IMAGE_EXTENSIONS` | Список поддерживаемых extensions |
237
+ | `isImageExtension` | Регистронезависимая проверка extension |
238
+ | `isDomReference` | Floating reference является `Element` |
239
+ | `validateUrl` | Консервативный UI URL result |
@@ -0,0 +1,138 @@
1
+ import { Meta } from "@storybook/addon-docs/blocks";
2
+
3
+ <Meta title="Foundation Lib/React/Virtualizer Pagination" />
4
+
5
+ # Дозагрузка virtualized списка через `@ryuzaki13/react-foundation-lib/virtualizer`
6
+
7
+ `useFetchNextPageEffect` вызывает `fetchNextPage`, когда последняя отрендеренная virtual item достигает конца уже загруженного списка.
8
+
9
+ ## Установка и импорт
10
+
11
+ ```bash
12
+ npm install react @tanstack/react-virtual @ryuzaki13/react-foundation-lib
13
+ ```
14
+
15
+ ```ts
16
+ import { useFetchNextPageEffect } from "@ryuzaki13/react-foundation-lib/virtualizer";
17
+ ```
18
+
19
+ Host должен использовать совместимые peer dependencies React `>=19.2 <20` и `@tanstack/react-virtual >=3.14 <4`.
20
+
21
+ ## Назначение и границы
22
+
23
+ ```text
24
+ virtualItems (видимый диапазон)
25
+ currentItemsCount (сколько уже загружено)
26
+ hasNextPage
27
+ │
28
+ последний virtual index >= currentItemsCount - 1
29
+ │
30
+ ▼
31
+ fetchNextPage() один раз до завершения Promise
32
+ ```
33
+
34
+ Hook не создаёт virtualizer, не хранит backend data, не строит query key и не решает server pagination. TanStack Query должен оставаться владельцем server snapshot.
35
+
36
+ ## Сигнатура
37
+
38
+ ```ts
39
+ useFetchNextPageEffect({
40
+ virtualItems,
41
+ currentItemsCount,
42
+ fetchNextPage,
43
+ hasNextPage
44
+ });
45
+ ```
46
+
47
+ | Поле | Значение |
48
+ | ------------------- | ------------------------------------ |
49
+ | `virtualItems` | Текущий массив `VirtualItem[]` |
50
+ | `currentItemsCount` | Число реально загруженных data items |
51
+ | `hasNextPage` | Есть ли следующая server page |
52
+ | `fetchNextPage` | Stable функция, возвращающая Promise |
53
+
54
+ Hook ничего не возвращает.
55
+
56
+ ## Пример с TanStack Query/Virtual
57
+
58
+ ```tsx
59
+ const rows = query.data?.pages.flatMap((page) => page.rows) ?? [];
60
+ const virtualItems = virtualizer.getVirtualItems();
61
+
62
+ useFetchNextPageEffect({
63
+ virtualItems,
64
+ currentItemsCount: rows.length,
65
+ hasNextPage: query.hasNextPage,
66
+ fetchNextPage: async () => {
67
+ await query.fetchNextPage();
68
+ }
69
+ });
70
+ ```
71
+
72
+ Передавайте количество загруженных строк, а не virtual total с loader row, group headers или estimated backend count.
73
+
74
+ ## Точный trigger
75
+
76
+ Fetch не запускается, если:
77
+
78
+ - `virtualItems` пуст;
79
+ - `currentItemsCount` равен `0`;
80
+ - последняя virtual item ещё не достигла `currentItemsCount - 1`;
81
+ - `hasNextPage === false`;
82
+ - предыдущий вызов ещё не завершился.
83
+
84
+ Trigger происходит строго на последнем загруженном индексе. Preload threshold «за N строк до конца» в API отсутствует.
85
+
86
+ ## Защита от параллельного fetch
87
+
88
+ Внутренний `useRef(false)` устанавливается до вызова и сбрасывается в `.finally()`:
89
+
90
+ ```text
91
+ idle ── trigger ──► fetchingRef=true
92
+ │
93
+ Promise pending
94
+ │
95
+ resolve или reject
96
+ ▼
97
+ fetchingRef=false
98
+ ```
99
+
100
+ Защита действует только внутри одного mounted hook instance. TanStack Query также должен владеть дедупликацией/network state.
101
+
102
+ Если `fetchNextPage` отклоняет Promise, hook не добавляет `.catch`; rejection может стать unhandled. Передавайте функцию с принятой error policy или wrapper, который корректно обрабатывает ожидаемую ошибку.
103
+
104
+ ## Referential stability
105
+
106
+ Effect зависит от `virtualItems`, `currentItemsCount`, `hasNextPage`, `fetchNextPage`.
107
+
108
+ - Используйте значение virtualizer как предусмотрено его API.
109
+ - Не создавайте лишний async callback с новой identity без причины.
110
+ - React Compiler не отменяет необходимость stable external contract.
111
+ - После увеличения `currentItemsCount` новый fetch произойдёт только когда virtual range достигнет нового конца.
112
+
113
+ ## Browser, SSR и lifecycle
114
+
115
+ Сам hook не читает `window`, но является React effect и не выполняет fetch во время server render. В browser effect запускается после commit.
116
+
117
+ При unmount Promise не отменяется. Abort/cancel должен быть реализован query/transport owner. `fetchingRef` больше не влияет после удаления hook instance.
118
+
119
+ ## Типичные ошибки
120
+
121
+ - Передать total backend rows вместо количества загруженного массива.
122
+ - Включить client-side sorting/filtering одной страницы при server-side модели.
123
+ - Добавить фиктивный loader row в `currentItemsCount`.
124
+ - Ожидать preload заранее — trigger находится на самом конце.
125
+ - Передать rejecting Promise без error handling.
126
+ - Выполнять fetch прямо в row render вместо effect.
127
+ - Использовать изменяемый index как domain identity строки.
128
+
129
+ ## API
130
+
131
+ ```ts
132
+ function useFetchNextPageEffect(args: {
133
+ virtualItems: VirtualItem[];
134
+ currentItemsCount: number;
135
+ hasNextPage: boolean;
136
+ fetchNextPage: () => Promise<unknown>;
137
+ }): void;
138
+ ```
@@ -0,0 +1,149 @@
1
+ import { Meta } from "@storybook/addon-docs/blocks";
2
+
3
+ <Meta title="Foundation Lib/Data/XML" />
4
+
5
+ # Простые XML helpers через `@ryuzaki13/react-foundation-lib/xml`
6
+
7
+ Модуль экранирует XML-текст, строит последовательность простых полей и читает текст первого тега через `DOMParser`.
8
+
9
+ ## Содержание
10
+
11
+ - [Назначение и границы](#назначение-и-границы)
12
+ - [Установка и импорт](#установка-и-импорт)
13
+ - [`escapeXmlValue`](#escapexmlvalue)
14
+ - [`buildXmlFields`](#buildxmlfields)
15
+ - [`getXmlTagText`](#getxmltagtext)
16
+ - [Безопасность и ограничения](#безопасность-и-ограничения)
17
+ - [API](#api)
18
+
19
+ ## Назначение и границы
20
+
21
+ Подходит для небольших flat XML fragments:
22
+
23
+ ```xml
24
+ <ORDER_ID>42</ORDER_ID><COMMENT>A&amp;B</COMMENT>
25
+ ```
26
+
27
+ Модуль не является полноценным XML builder/parser:
28
+
29
+ - не строит document/envelope;
30
+ - не управляет namespaces и attributes;
31
+ - не валидирует XSD;
32
+ - не обрабатывает коллекции/вложенные объекты;
33
+ - не проверяет parser errors;
34
+ - не выполняет network request.
35
+
36
+ ## Установка и импорт
37
+
38
+ ```ts
39
+ import { buildXmlFields, escapeXmlValue, getXmlTagText } from "@ryuzaki13/react-foundation-lib/xml";
40
+
41
+ import type { XmlPrimitive } from "@ryuzaki13/react-foundation-lib/xml";
42
+ ```
43
+
44
+ Builder-функции не требуют DOM. `getXmlTagText` требует global `DOMParser`, доступный в браузере/jsdom, но обычно отсутствующий в чистом Node.js.
45
+
46
+ ## `escapeXmlValue`
47
+
48
+ ```ts
49
+ escapeXmlValue(`a&b<c>d"e'f`);
50
+ // "a&amp;b&lt;c&gt;d&quot;e&apos;f"
51
+ ```
52
+
53
+ Тип входа:
54
+
55
+ ```ts
56
+ type XmlPrimitive = string | number | null | undefined;
57
+ ```
58
+
59
+ Правила:
60
+
61
+ | Символ | XML entity |
62
+ | ------ | ---------- |
63
+ | `&` | `&amp;` |
64
+ | `<` | `&lt;` |
65
+ | `>` | `&gt;` |
66
+ | `"` | `&quot;` |
67
+ | `'` | `&apos;` |
68
+
69
+ `null` и `undefined` превращаются в пустую строку. Number сначала преобразуется через `String`.
70
+
71
+ Функция предназначена для текстового содержимого. Для attribute value она экранирует основные символы, но сам attribute builder модуль не предоставляет.
72
+
73
+ Не передавайте уже экранированную строку повторно:
74
+
75
+ ```ts
76
+ escapeXmlValue("A&amp;B"); // "A&amp;amp;B".
77
+ ```
78
+
79
+ ## `buildXmlFields`
80
+
81
+ ```ts
82
+ const xml = buildXmlFields({
83
+ ORDER_ID: 42,
84
+ COMMENT: "A&B",
85
+ OPTIONAL: null
86
+ });
87
+
88
+ // <ORDER_ID>42</ORDER_ID><COMMENT>A&amp;B</COMMENT><OPTIONAL></OPTIONAL>
89
+ ```
90
+
91
+ Функция:
92
+
93
+ - обходит `Object.entries` в стандартном порядке ключей;
94
+ - строит для каждого поля `<NAME>escaped value</NAME>`;
95
+ - не добавляет переносы строк;
96
+ - сохраняет поля с `null`/`undefined` как пустые теги;
97
+ - не изменяет исходный объект.
98
+
99
+ Имя тега вставляется без валидации и escaping. Ключи должны быть доверенными валидными XML names из программного contract:
100
+
101
+ ```ts
102
+ // Нельзя строить key из пользовательского ввода.
103
+ buildXmlFields({ [userProvidedTagName]: value });
104
+ ```
105
+
106
+ Для вложенной структуры передайте уже собранный fragment как часть внешнего builder-а; `buildXmlFields` сам воспринимает строку как текст и экранирует markup.
107
+
108
+ ## `getXmlTagText`
109
+
110
+ ```ts
111
+ const message = getXmlTagText("<root><EX_MESSAGE> Ошибка </EX_MESSAGE></root>", "EX_MESSAGE");
112
+
113
+ console.log(message); // "Ошибка".
114
+ ```
115
+
116
+ Алгоритм:
117
+
118
+ 1. Парсит всю строку как `application/xml`.
119
+ 2. Вызывает `getElementsByTagName(tagName)`.
120
+ 3. Берёт только первый найденный element.
121
+ 4. Читает объединённый `textContent`.
122
+ 5. Обрезает пробелы.
123
+ 6. Возвращает `undefined`, если элемента/непустого текста нет.
124
+
125
+ ```ts
126
+ getXmlTagText("<root />", "MESSAGE"); // undefined.
127
+ getXmlTagText("<root><MESSAGE> </MESSAGE></root>", "MESSAGE"); // undefined.
128
+ ```
129
+
130
+ Для namespace-sensitive XML точное поведение `getElementsByTagName` зависит от переданного имени. Модуль не предоставляет `getElementsByTagNameNS`.
131
+
132
+ ## Безопасность и ограничения
133
+
134
+ - Не вставляйте результат XML в HTML DOM через `innerHTML`.
135
+ - Не используйте dynamic tag names из недоверенного ввода.
136
+ - Parser не проверяется на `<parsererror>`; malformed XML обычно даёт `undefined` или parser-specific tree, а не явное исключение этого helper.
137
+ - Читается только первое совпадение.
138
+ - Большие documents лучше разбирать специализированным parser/streaming boundary.
139
+ - Модуль не предотвращает XML signature wrapping и не предназначен для security validation.
140
+ - `XmlPrimitive` number допускает `NaN`/`Infinity` на уровне TypeScript; они станут строками `"NaN"`/`"Infinity"`.
141
+
142
+ ## API
143
+
144
+ | Export | Результат |
145
+ | ---------------- | --------------------------------------- |
146
+ | `escapeXmlValue` | Экранированная строка |
147
+ | `buildXmlFields` | Компактный XML fragment |
148
+ | `getXmlTagText` | `string \| undefined` |
149
+ | `XmlPrimitive` | `string \| number \| null \| undefined` |