@ryuzaki13/react-foundation-lib 1.2.1 → 1.2.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.
- package/package.json +3 -1
- package/src/binary/README.md +110 -0
- package/src/file/README.md +289 -0
- package/src/file/file.mdx +6 -0
- package/src/formatters/date/README.md +459 -0
- package/src/formatters/date/date.mdx +6 -0
- package/src/formatters/number/README.md +123 -0
- package/src/formatters/number/number.mdx +6 -0
- package/src/formatters/valueState/README.md +275 -0
- package/src/formatters/valueState/valueState.mdx +6 -0
- package/src/formulas/README.md +42 -0
- package/src/formulas/formulas.mdx +6 -0
- package/src/pwa/README.md +638 -0
- package/src/pwa/pwa.mdx +6 -0
- package/src/query-client/README.md +565 -0
- package/src/query-client/queryClient.mdx +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryuzaki13/react-foundation-lib",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Reusable React and TypeScript foundation utilities, hooks, and primitives.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ryuzaki13",
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
"sideEffects": false,
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|
|
30
|
+
"src/**/*.mdx",
|
|
31
|
+
"src/**/README.md",
|
|
30
32
|
"README.md",
|
|
31
33
|
"LICENSE"
|
|
32
34
|
],
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# `shared/lib/binary`
|
|
2
|
+
|
|
3
|
+
Модуль содержит небольшие утилиты для работы с бинарным содержимым, которое приходит в приложение строкой. Основной сценарий — получить из SAP/OData/REST-ответа base64-документ, определить его MIME-тип и передать дальше как `Blob` в просмотрщик, ссылку скачивания или API браузера.
|
|
4
|
+
|
|
5
|
+
Модуль находится в `shared`, поэтому не содержит бизнес-знаний о типах документов конкретного приложения. Например, `detectMimeType` не использует коды вроде `documentType`: MIME определяется только по имени файла, data URL или сигнатуре самих данных.
|
|
6
|
+
|
|
7
|
+
## Публичный API
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { binaryToBlob, detectMimeType, useBinaryFile } from "@/shared/lib";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Также модуль реэкспортируется через общий `@/shared/lib`.
|
|
14
|
+
|
|
15
|
+
### `binaryToBlob`
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
binaryToBlob(binaryData: string, mimeType?: string, isBase64?: boolean): Blob
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Преобразует строковое бинарное содержимое в `Blob`.
|
|
22
|
+
|
|
23
|
+
Параметры:
|
|
24
|
+
|
|
25
|
+
- `binaryData` — исходные данные. По умолчанию ожидается base64-строка.
|
|
26
|
+
- `mimeType` — MIME-тип создаваемого Blob. Если передана пустая строка, используется `application/octet-stream`.
|
|
27
|
+
- `isBase64` — признак формата входных данных. По умолчанию `true`. Если передать `false`, строка будет обработана как raw binary string.
|
|
28
|
+
|
|
29
|
+
Поддерживаются:
|
|
30
|
+
|
|
31
|
+
- обычный base64;
|
|
32
|
+
- base64 с пробелами и переносами строк;
|
|
33
|
+
- URL-safe base64 с символами `-` и `_`;
|
|
34
|
+
- data URL вида `data:application/pdf;base64,...`.
|
|
35
|
+
|
|
36
|
+
Функция возвращает именно `Blob`. Object URL нужно создавать отдельно через `URL.createObjectURL(blob)` и освобождать через `URL.revokeObjectURL(url)` на стороне вызывающего кода.
|
|
37
|
+
|
|
38
|
+
Пример:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const mime = detectMimeType(fileName, fileContent);
|
|
42
|
+
const blob = binaryToBlob(fileContent, mime);
|
|
43
|
+
const url = URL.createObjectURL(blob);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### `detectMimeType`
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
detectMimeType(fileName?: string, binaryData?: string): string
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Определяет MIME-тип без привязки к бизнес-типу документа.
|
|
53
|
+
|
|
54
|
+
Порядок определения:
|
|
55
|
+
|
|
56
|
+
1. По расширению `fileName`.
|
|
57
|
+
2. По MIME-типу из data URL.
|
|
58
|
+
3. По сигнатуре первых байтов base64-данных.
|
|
59
|
+
4. Fallback `application/pdf`.
|
|
60
|
+
|
|
61
|
+
Поддерживаемые расширения включают распространённые документы и изображения: `pdf`, `jpg`, `jpeg`, `png`, `gif`, `webp`, `svg`, `bmp`, `tif`, `tiff`, `xml`, `txt`, `csv`, `json`, `doc`, `docx`, `xls`, `xlsx`, `ppt`, `pptx`, `zip`.
|
|
62
|
+
|
|
63
|
+
По сигнатуре определяются:
|
|
64
|
+
|
|
65
|
+
- PDF;
|
|
66
|
+
- JPEG;
|
|
67
|
+
- PNG;
|
|
68
|
+
- GIF;
|
|
69
|
+
- WEBP;
|
|
70
|
+
- ZIP;
|
|
71
|
+
- XML-подобные текстовые данные.
|
|
72
|
+
|
|
73
|
+
Fallback `application/pdf` сохранён намеренно: большинство документов в текущем контуре отображаются как PDF, а неизвестный тип чаще должен открыться в документном просмотрщике, чем стать произвольным `application/octet-stream`.
|
|
74
|
+
|
|
75
|
+
### `useBinaryFile`
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
useBinaryFile(bin: string | undefined): {
|
|
79
|
+
data: { blob: Blob; mime: string } | null;
|
|
80
|
+
error: string | null;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
React-хук для компонентов, которым нужно получить готовую пару `Blob + MIME` из base64-строки.
|
|
85
|
+
|
|
86
|
+
Поведение:
|
|
87
|
+
|
|
88
|
+
- при наличии `bin` определяет MIME через `detectMimeType`;
|
|
89
|
+
- создаёт `Blob` через `binaryToBlob`;
|
|
90
|
+
- пишет ошибку в `error`, если данные некорректны;
|
|
91
|
+
- очищает `data` и `error`, когда `bin` становится пустым.
|
|
92
|
+
|
|
93
|
+
Хук не создаёт object URL и не управляет его жизненным циклом. Это должно оставаться в компоненте, потому что именно компонент знает, когда URL больше не нужен.
|
|
94
|
+
|
|
95
|
+
## Ограничения
|
|
96
|
+
|
|
97
|
+
- Модуль рассчитан на браузерное окружение и использует `Blob` и `atob`.
|
|
98
|
+
- MIME по сигнатуре определяется только для ограниченного набора популярных форматов.
|
|
99
|
+
- Функции не валидируют содержимое файла полностью. Проверяется только формат base64 и несколько первых байтов для MIME-detection.
|
|
100
|
+
- Модуль не должен импортировать сущности, фичи, виджеты или приложения. Если нужен бизнес-маппинг кодов документов, его нужно держать в соответствующем доменном слое и передавать результат в этот модуль уже как MIME-строку.
|
|
101
|
+
|
|
102
|
+
## Проверки
|
|
103
|
+
|
|
104
|
+
Поведение покрыто тестами в `binaryToBlob.test.ts`.
|
|
105
|
+
|
|
106
|
+
Релевантный узкий запуск:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npm run test -- run src/shared/lib/binary/binaryToBlob.test.ts
|
|
110
|
+
```
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# Библиотека чтения файлов
|
|
2
|
+
|
|
3
|
+
Библиотека для безопасного чтения файлов на клиенте через `FileReader` API с валидацией, поддержкой отмены и строгой типизацией.
|
|
4
|
+
|
|
5
|
+
## Структура
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/shared/lib/file/
|
|
9
|
+
├── read-file/ — универсальное чтение любых файлов
|
|
10
|
+
│ ├── types.ts
|
|
11
|
+
│ ├── readFile.ts
|
|
12
|
+
│ └── index.ts
|
|
13
|
+
├── image/ — чтение изображений (надстройка над read-file)
|
|
14
|
+
│ ├── types.ts
|
|
15
|
+
│ ├── readImageFile.ts
|
|
16
|
+
│ └── index.ts
|
|
17
|
+
└── index.ts
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Все экспорты доступны через `@/shared/lib`.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## `readFile` — чтение любых файлов
|
|
25
|
+
|
|
26
|
+
Универсальная функция для чтения файлов. Поддерживает два режима: `data-url` (по умолчанию) и `array-buffer`.
|
|
27
|
+
|
|
28
|
+
### Сигнатуры
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// Чтение как Data URL (по умолчанию)
|
|
32
|
+
readFile(file: File, opts?: { mode?: "data-url"; ... }): Promise<ReadFileAsDataUrlResult>
|
|
33
|
+
|
|
34
|
+
// Чтение как ArrayBuffer
|
|
35
|
+
readFile(file: File, opts: { mode: "array-buffer"; ... }): Promise<ReadFileAsArrayBufferResult>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Опции (`ReadFileOptions`)
|
|
39
|
+
|
|
40
|
+
| Параметр | Тип | По умолчанию | Описание |
|
|
41
|
+
| ------------- | ------------------------------ | ------------ | ---------------------------------- |
|
|
42
|
+
| `mode` | `"data-url" \| "array-buffer"` | `"data-url"` | Режим чтения файла |
|
|
43
|
+
| `allowedMime` | `readonly string[]` | — | Список разрешённых MIME-типов |
|
|
44
|
+
| `maxBytes` | `number` | — | Максимальный размер файла в байтах |
|
|
45
|
+
| `signal` | `AbortSignal` | — | Сигнал для отмены операции |
|
|
46
|
+
|
|
47
|
+
### Результат
|
|
48
|
+
|
|
49
|
+
**`ReadFileAsDataUrlResult`** (при `mode: "data-url"`):
|
|
50
|
+
|
|
51
|
+
| Поле | Тип | Описание |
|
|
52
|
+
| --------- | ------------ | ----------------------------- |
|
|
53
|
+
| `mode` | `"data-url"` | Дискриминант режима |
|
|
54
|
+
| `meta` | `FileMeta` | Метаданные файла |
|
|
55
|
+
| `dataUrl` | `string` | Содержимое в формате Data URL |
|
|
56
|
+
| `file` | `File` | Исходный объект `File` |
|
|
57
|
+
|
|
58
|
+
**`ReadFileAsArrayBufferResult`** (при `mode: "array-buffer"`):
|
|
59
|
+
|
|
60
|
+
| Поле | Тип | Описание |
|
|
61
|
+
| -------- | ---------------- | ---------------------- |
|
|
62
|
+
| `mode` | `"array-buffer"` | Дискриминант режима |
|
|
63
|
+
| `meta` | `FileMeta` | Метаданные файла |
|
|
64
|
+
| `buffer` | `ArrayBuffer` | Бинарное содержимое |
|
|
65
|
+
| `file` | `File` | Исходный объект `File` |
|
|
66
|
+
|
|
67
|
+
**`FileMeta`** — метаданные файла:
|
|
68
|
+
|
|
69
|
+
| Поле | Тип | Описание |
|
|
70
|
+
| -------------- | -------- | -------------------------------------- |
|
|
71
|
+
| `mime` | `string` | MIME-тип файла |
|
|
72
|
+
| `size` | `number` | Размер в байтах |
|
|
73
|
+
| `name` | `string` | Имя файла |
|
|
74
|
+
| `lastModified` | `number` | Время последнего изменения (timestamp) |
|
|
75
|
+
|
|
76
|
+
### Примеры
|
|
77
|
+
|
|
78
|
+
**Чтение PDF-файла как Data URL для предпросмотра:**
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { readFile, ReadFileError } from "@/shared/lib";
|
|
82
|
+
|
|
83
|
+
async function handlePdfUpload(file: File) {
|
|
84
|
+
try {
|
|
85
|
+
const result = await readFile(file, {
|
|
86
|
+
allowedMime: ["application/pdf"],
|
|
87
|
+
maxBytes: 50 * 1024 * 1024, // 50 МБ
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// result.mode === "data-url" (тип сужен автоматически)
|
|
91
|
+
console.log(result.meta.name); // "document.pdf"
|
|
92
|
+
console.log(result.meta.size); // 1234567
|
|
93
|
+
previewIframe.src = result.dataUrl;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (error instanceof ReadFileError) {
|
|
96
|
+
console.error(error.code, error.message);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Чтение файла как ArrayBuffer для отправки на сервер:**
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { readFile } from "@/shared/lib";
|
|
106
|
+
|
|
107
|
+
async function uploadDocument(file: File) {
|
|
108
|
+
const result = await readFile(file, {
|
|
109
|
+
mode: "array-buffer",
|
|
110
|
+
maxBytes: 100 * 1024 * 1024, // 100 МБ
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// result.buffer: ArrayBuffer (тип сужен автоматически)
|
|
114
|
+
await fetch("/api/upload", {
|
|
115
|
+
method: "POST",
|
|
116
|
+
body: result.buffer,
|
|
117
|
+
headers: { "Content-Type": result.meta.mime },
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Чтение с поддержкой отмены:**
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { readFile } from "@/shared/lib";
|
|
126
|
+
|
|
127
|
+
const controller = new AbortController();
|
|
128
|
+
|
|
129
|
+
// Отмена через 5 секунд
|
|
130
|
+
setTimeout(() => controller.abort(), 5000);
|
|
131
|
+
|
|
132
|
+
const result = await readFile(file, {
|
|
133
|
+
signal: controller.signal,
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Чтение без ограничений (любой файл):**
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { readFile } from "@/shared/lib";
|
|
141
|
+
|
|
142
|
+
const result = await readFile(file);
|
|
143
|
+
// result.meta.mime — реальный MIME-тип файла
|
|
144
|
+
// result.dataUrl — Data URL для любого типа
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## `readImageFile` — чтение изображений
|
|
150
|
+
|
|
151
|
+
Надстройка над `readFile` для работы с изображениями. Добавляет:
|
|
152
|
+
|
|
153
|
+
- Проверку что файл является изображением (`image/*`)
|
|
154
|
+
- Типизацию `allowedMime` на `ImageMime` (только image-типы)
|
|
155
|
+
- Автоматическое извлечение размеров изображения (`dimensions`) в режиме `data-url`
|
|
156
|
+
|
|
157
|
+
### Сигнатуры
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
// Чтение как Data URL с размерами (по умолчанию)
|
|
161
|
+
readImageFile(file: File, opts?: { mode?: "data-url"; ... }): Promise<ReadImageAsDataUrlResult>
|
|
162
|
+
|
|
163
|
+
// Чтение как ArrayBuffer
|
|
164
|
+
readImageFile(file: File, opts: { mode: "array-buffer"; ... }): Promise<ReadImageAsArrayBufferResult>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Опции (`ReadImageOptions`)
|
|
168
|
+
|
|
169
|
+
| Параметр | Тип | По умолчанию | Описание |
|
|
170
|
+
| ------------- | ------------------------------ | ------------ | ---------------------------------- |
|
|
171
|
+
| `mode` | `"data-url" \| "array-buffer"` | `"data-url"` | Режим чтения файла |
|
|
172
|
+
| `allowedMime` | `readonly ImageMime[]` | — | Разрешённые image MIME-типы |
|
|
173
|
+
| `maxBytes` | `number` | — | Максимальный размер файла в байтах |
|
|
174
|
+
| `signal` | `AbortSignal` | — | Сигнал для отмены операции |
|
|
175
|
+
|
|
176
|
+
**`ImageMime`** — допустимые значения:
|
|
177
|
+
`"image/png"`, `"image/jpeg"`, `"image/webp"`, `"image/gif"`, `"image/svg+xml"`, `"image/avif"`
|
|
178
|
+
|
|
179
|
+
### Результат
|
|
180
|
+
|
|
181
|
+
**`ReadImageAsDataUrlResult`** расширяет `ReadFileAsDataUrlResult`:
|
|
182
|
+
|
|
183
|
+
| Поле | Тип | Описание |
|
|
184
|
+
| ------------ | ------------------ | -------------------------------- |
|
|
185
|
+
| `mode` | `"data-url"` | Дискриминант режима |
|
|
186
|
+
| `meta` | `FileMeta` | Метаданные файла |
|
|
187
|
+
| `dataUrl` | `string` | Содержимое в формате Data URL |
|
|
188
|
+
| `file` | `File` | Исходный объект `File` |
|
|
189
|
+
| `dimensions` | `ImageDimensions?` | Ширина и высота изображения (px) |
|
|
190
|
+
|
|
191
|
+
**`ReadImageAsArrayBufferResult`** — идентичен `ReadFileAsArrayBufferResult`.
|
|
192
|
+
|
|
193
|
+
### Примеры
|
|
194
|
+
|
|
195
|
+
**Загрузка фото с ограничениями:**
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
import { readImageFile, ReadImageError } from "@/shared/lib";
|
|
199
|
+
|
|
200
|
+
async function selectPhoto(file: File) {
|
|
201
|
+
try {
|
|
202
|
+
const result = await readImageFile(file, {
|
|
203
|
+
allowedMime: ["image/jpeg", "image/png", "image/webp"],
|
|
204
|
+
maxBytes: 10 * 1024 * 1024, // 10 МБ
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
console.log(result.dimensions); // { width: 1920, height: 1080 }
|
|
208
|
+
preview.src = result.dataUrl;
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (error instanceof ReadImageError) {
|
|
211
|
+
switch (error.code) {
|
|
212
|
+
case "NOT_AN_IMAGE":
|
|
213
|
+
alert("Выберите изображение");
|
|
214
|
+
break;
|
|
215
|
+
case "MIME_NOT_ALLOWED":
|
|
216
|
+
alert("Формат не поддерживается");
|
|
217
|
+
break;
|
|
218
|
+
case "FILE_TOO_LARGE":
|
|
219
|
+
alert("Файл слишком большой");
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Чтение изображения как ArrayBuffer:**
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
import { readImageFile } from "@/shared/lib";
|
|
231
|
+
|
|
232
|
+
const result = await readImageFile(file, { mode: "array-buffer" });
|
|
233
|
+
// result.buffer: ArrayBuffer
|
|
234
|
+
// dimensions не извлекаются в этом режиме
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Обработка ошибок
|
|
240
|
+
|
|
241
|
+
### `ReadFileError`
|
|
242
|
+
|
|
243
|
+
Базовый класс ошибок для `readFile`.
|
|
244
|
+
|
|
245
|
+
| Код | Когда возникает |
|
|
246
|
+
| ------------------ | ---------------------------------------------------- |
|
|
247
|
+
| `NO_FILE` | Файл не передан |
|
|
248
|
+
| `MIME_NOT_ALLOWED` | MIME-тип файла не входит в `allowedMime` |
|
|
249
|
+
| `FILE_TOO_LARGE` | Размер файла превышает `maxBytes` |
|
|
250
|
+
| `READ_ABORTED` | Чтение прервано через `AbortSignal` или `FileReader` |
|
|
251
|
+
| `READ_FAILED` | Ошибка `FileReader` при чтении |
|
|
252
|
+
|
|
253
|
+
### `ReadImageError`
|
|
254
|
+
|
|
255
|
+
Класс ошибок для `readImageFile`. Наследует все коды из `ReadFileErrorCode` и добавляет:
|
|
256
|
+
|
|
257
|
+
| Код | Когда возникает |
|
|
258
|
+
| --------------------- | ---------------------------------------------------------- |
|
|
259
|
+
| `NOT_AN_IMAGE` | MIME-тип файла не начинается с `image/` |
|
|
260
|
+
| `IMAGE_DECODE_FAILED` | Не удалось декодировать изображение для получения размеров |
|
|
261
|
+
|
|
262
|
+
### Пример обработки ошибок
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
import { readFile, ReadFileError } from "@/shared/lib";
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const result = await readFile(file, { maxBytes: 5 * 1024 * 1024 });
|
|
269
|
+
} catch (error) {
|
|
270
|
+
if (error instanceof ReadFileError) {
|
|
271
|
+
// error.code — строковый код ошибки
|
|
272
|
+
// error.message — описание на русском
|
|
273
|
+
// error.cause — оригинальная ошибка (если есть)
|
|
274
|
+
console.error(`[${error.code}] ${error.message}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Утилиты
|
|
282
|
+
|
|
283
|
+
Экспортируются из `read-file` для переиспользования в надстройках:
|
|
284
|
+
|
|
285
|
+
| Функция | Описание |
|
|
286
|
+
| ----------------------------- | -------------------------------------------------------------------- |
|
|
287
|
+
| `buildMeta(file)` | Извлекает `FileMeta` из объекта `File` |
|
|
288
|
+
| `assertNotAborted(signal?)` | Бросает `ReadFileError("READ_ABORTED")` если сигнал отменён |
|
|
289
|
+
| `attachAbort(signal, reader)` | Привязывает `AbortSignal` к `FileReader`, возвращает функцию отписки |
|