jtcsv 1.2.0 → 2.1.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 +252 -337
- package/bin/jtcsv.js +167 -85
- package/cli-tui.js +0 -0
- package/dist/jtcsv.cjs.js +1619 -0
- package/dist/jtcsv.cjs.js.map +1 -0
- package/dist/jtcsv.esm.js +1599 -0
- package/dist/jtcsv.esm.js.map +1 -0
- package/dist/jtcsv.umd.js +1625 -0
- package/dist/jtcsv.umd.js.map +1 -0
- package/examples/cli-tool.js +186 -0
- package/examples/express-api.js +167 -0
- package/examples/large-dataset-example.js +185 -0
- package/examples/plugin-excel-exporter.js +407 -0
- package/examples/simple-usage.js +280 -0
- package/examples/streaming-example.js +419 -0
- package/index.d.ts +4 -0
- package/json-save.js +1 -1
- package/package.json +128 -14
- package/plugins/README.md +373 -0
- package/plugins/express-middleware/README.md +306 -0
- package/plugins/express-middleware/example.js +136 -0
- package/plugins/express-middleware/index.d.ts +114 -0
- package/plugins/express-middleware/index.js +360 -0
- package/plugins/express-middleware/package.json +52 -0
- package/plugins/fastify-plugin/index.js +406 -0
- package/plugins/fastify-plugin/package.json +55 -0
- package/plugins/nextjs-api/README.md +452 -0
- package/plugins/nextjs-api/examples/ConverterComponent.jsx +386 -0
- package/plugins/nextjs-api/examples/api-convert.js +69 -0
- package/plugins/nextjs-api/index.js +388 -0
- package/plugins/nextjs-api/package.json +63 -0
- package/plugins/nextjs-api/route.js +372 -0
- package/src/browser/browser-functions.js +189 -0
- package/src/browser/csv-to-json-browser.js +442 -0
- package/src/browser/errors-browser.js +194 -0
- package/src/browser/index.js +79 -0
- package/src/browser/json-to-csv-browser.js +309 -0
- package/src/browser/workers/csv-parser.worker.js +359 -0
- package/src/browser/workers/worker-pool.js +467 -0
- package/src/core/plugin-system.js +472 -0
- package/src/engines/fast-path-engine-new.js +338 -0
- package/src/engines/fast-path-engine.js +347 -0
- package/src/formats/ndjson-parser.js +419 -0
- package/src/index-with-plugins.js +349 -0
- package/stream-csv-to-json.js +1 -1
- package/stream-json-to-csv.js +1 -1
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js API Route для JTCSV
|
|
3
|
+
* Готовый API endpoint для конвертации CSV/JSON в Next.js приложениях
|
|
4
|
+
*
|
|
5
|
+
* @version 1.0.0
|
|
6
|
+
* @date 2026-01-23
|
|
7
|
+
*
|
|
8
|
+
* @usage
|
|
9
|
+
* 1. Скопируйте этот файл в pages/api/convert.js
|
|
10
|
+
* 2. Или импортируйте функции в существующие API routes
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { csvToJson, jsonToCsv } from 'jtcsv
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Конфигурация Next.js API route
|
|
17
|
+
*/
|
|
18
|
+
export const config = {
|
|
19
|
+
api: {
|
|
20
|
+
bodyParser: {
|
|
21
|
+
sizeLimit: '50mb', // Максимальный размер тела запроса
|
|
22
|
+
|
|
23
|
+
// Кастомный парсер для определения формата
|
|
24
|
+
parse: (req) => {
|
|
25
|
+
const contentType = req.headers['content-type'] || '';
|
|
26
|
+
|
|
27
|
+
if (contentType.includes('application/json')) {
|
|
28
|
+
return JSON.parse(req.body);
|
|
29
|
+
} else if (contentType.includes('text/csv') || contentType.includes('text/plain')) {
|
|
30
|
+
return req.body;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Пытаемся определить автоматически
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(req.body);
|
|
36
|
+
} catch {
|
|
37
|
+
return req.body;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Основной обработчик API route
|
|
46
|
+
*
|
|
47
|
+
* @param {import('next').NextApiRequest} req - Next.js request object
|
|
48
|
+
* @param {import('next').NextApiResponse} res - Next.js response object
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* // Пример запроса:
|
|
52
|
+
* // POST /api/convert
|
|
53
|
+
* // Content-Type: application/json
|
|
54
|
+
* // Body: [{ "name": "John", "age": 30 }]
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // Пример запроса:
|
|
58
|
+
* // POST /api/convert?format=csv
|
|
59
|
+
* // Content-Type: text/csv
|
|
60
|
+
* // Body: name,age\nJohn,30\nJane,25
|
|
61
|
+
*/
|
|
62
|
+
export default async function handler(req, res) {
|
|
63
|
+
// Поддерживаем только POST запросы
|
|
64
|
+
if (req.method !== 'POST') {
|
|
65
|
+
return res.status(405).json({
|
|
66
|
+
success: false,
|
|
67
|
+
error: 'Method not allowed',
|
|
68
|
+
allowed: ['POST']
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const startTime = Date.now();
|
|
73
|
+
const contentType = req.headers['content-type'] || '';
|
|
74
|
+
const acceptHeader = req.headers['accept'] || 'application/json';
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const {
|
|
78
|
+
format,
|
|
79
|
+
delimiter = ',',
|
|
80
|
+
includeHeaders = 'true',
|
|
81
|
+
parseNumbers = 'true',
|
|
82
|
+
parseBooleans = 'true',
|
|
83
|
+
useFastPath = 'true',
|
|
84
|
+
preventCsvInjection = 'true'
|
|
85
|
+
} = req.query;
|
|
86
|
+
|
|
87
|
+
// Определяем желаемый формат вывода
|
|
88
|
+
let outputFormat = format || (acceptHeader.includes('text/csv') ? 'csv' : 'json');
|
|
89
|
+
|
|
90
|
+
// Определяем формат входных данных
|
|
91
|
+
let inputFormat = 'unknown';
|
|
92
|
+
|
|
93
|
+
if (contentType.includes('application/json') || Array.isArray(req.body)) {
|
|
94
|
+
inputFormat = 'json';
|
|
95
|
+
} else if (contentType.includes('text/csv') ||
|
|
96
|
+
contentType.includes('text/plain') ||
|
|
97
|
+
(typeof req.body === 'string' && req.body.includes(','))) {
|
|
98
|
+
inputFormat = 'csv';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (inputFormat === 'unknown') {
|
|
102
|
+
return res.status(400).json({
|
|
103
|
+
success: false,
|
|
104
|
+
error: 'Unable to determine input format',
|
|
105
|
+
code: 'UNKNOWN_FORMAT',
|
|
106
|
+
suggestions: [
|
|
107
|
+
'Set Content-Type header to application/json or text/csv',
|
|
108
|
+
'Or send JSON array/object or CSV string'
|
|
109
|
+
]
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Опции конвертации
|
|
114
|
+
const options = {
|
|
115
|
+
delimiter,
|
|
116
|
+
includeHeaders: includeHeaders === 'true',
|
|
117
|
+
parseNumbers: parseNumbers === 'true',
|
|
118
|
+
parseBooleans: parseBooleans === 'true',
|
|
119
|
+
useFastPath: useFastPath === 'true',
|
|
120
|
+
preventCsvInjection: preventCsvInjection === 'true',
|
|
121
|
+
rfc4180Compliant: true
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
let result;
|
|
125
|
+
let stats = {
|
|
126
|
+
inputSize: 0,
|
|
127
|
+
outputSize: 0,
|
|
128
|
+
processingTime: 0,
|
|
129
|
+
conversion: `${inputFormat}→${outputFormat}`
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Выполняем конвертацию
|
|
133
|
+
if (inputFormat === 'json' && outputFormat === 'csv') {
|
|
134
|
+
const jsonData = Array.isArray(req.body) ? req.body : [req.body];
|
|
135
|
+
stats.inputSize = Buffer.byteLength(JSON.stringify(jsonData));
|
|
136
|
+
|
|
137
|
+
result = await jsonToCsv(jsonData, options);
|
|
138
|
+
stats.outputSize = Buffer.byteLength(result);
|
|
139
|
+
|
|
140
|
+
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
141
|
+
res.setHeader('Content-Disposition', 'attachment; filename="data.csv"');
|
|
142
|
+
|
|
143
|
+
} else if (inputFormat === 'csv' && outputFormat === 'json') {
|
|
144
|
+
const csvData = typeof req.body === 'string' ? req.body : String(req.body);
|
|
145
|
+
stats.inputSize = Buffer.byteLength(csvData);
|
|
146
|
+
|
|
147
|
+
result = await csvToJson(csvData, options);
|
|
148
|
+
stats.outputSize = Buffer.byteLength(JSON.stringify(result));
|
|
149
|
+
|
|
150
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
151
|
+
|
|
152
|
+
} else {
|
|
153
|
+
// Нет необходимости в конвертации
|
|
154
|
+
result = req.body;
|
|
155
|
+
stats.conversion = 'none';
|
|
156
|
+
stats.inputSize = Buffer.byteLength(JSON.stringify(result));
|
|
157
|
+
stats.outputSize = stats.inputSize;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
stats.processingTime = Date.now() - startTime;
|
|
161
|
+
|
|
162
|
+
// Формируем ответ
|
|
163
|
+
const response = {
|
|
164
|
+
success: true,
|
|
165
|
+
data: result,
|
|
166
|
+
format: outputFormat,
|
|
167
|
+
inputFormat,
|
|
168
|
+
stats,
|
|
169
|
+
options: {
|
|
170
|
+
...options,
|
|
171
|
+
delimiter
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// Если запрашивали CSV, отправляем как plain text
|
|
176
|
+
if (outputFormat === 'csv') {
|
|
177
|
+
return res.status(200).send(result);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return res.status(200).json(response);
|
|
181
|
+
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error('Conversion error:', error);
|
|
184
|
+
|
|
185
|
+
const errorResponse = {
|
|
186
|
+
success: false,
|
|
187
|
+
error: error.message,
|
|
188
|
+
code: error.code || 'CONVERSION_ERROR',
|
|
189
|
+
timestamp: new Date().toISOString()
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Добавляем дополнительную информацию для отладки в development
|
|
193
|
+
if (process.env.NODE_ENV === 'development') {
|
|
194
|
+
errorResponse.stack = error.stack;
|
|
195
|
+
errorResponse.details = {
|
|
196
|
+
contentType: req.headers['content-type'],
|
|
197
|
+
contentLength: req.headers['content-length'],
|
|
198
|
+
method: req.method,
|
|
199
|
+
url: req.url,
|
|
200
|
+
query: req.query
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return res.status(400).json(errorResponse);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Специализированный обработчик для CSV → JSON
|
|
210
|
+
*/
|
|
211
|
+
export async function csvToJsonHandler(req, res) {
|
|
212
|
+
if (req.method !== 'POST') {
|
|
213
|
+
return res.status(405).json({
|
|
214
|
+
success: false,
|
|
215
|
+
error: 'Method not allowed',
|
|
216
|
+
allowed: ['POST']
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const startTime = Date.now();
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const csvData = req.body;
|
|
224
|
+
|
|
225
|
+
if (!csvData || (typeof csvData !== 'string' && !Buffer.isBuffer(csvData))) {
|
|
226
|
+
return res.status(400).json({
|
|
227
|
+
success: false,
|
|
228
|
+
error: 'CSV data is required'
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const {
|
|
233
|
+
delimiter = ',',
|
|
234
|
+
parseNumbers = 'true',
|
|
235
|
+
parseBooleans = 'true',
|
|
236
|
+
useFastPath = 'true'
|
|
237
|
+
} = req.query;
|
|
238
|
+
|
|
239
|
+
const result = await csvToJson(csvData, {
|
|
240
|
+
delimiter,
|
|
241
|
+
parseNumbers: parseNumbers === 'true',
|
|
242
|
+
parseBooleans: parseBooleans === 'true',
|
|
243
|
+
useFastPath: useFastPath === 'true',
|
|
244
|
+
preventCsvInjection: true,
|
|
245
|
+
rfc4180Compliant: true
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
return res.status(200).json({
|
|
249
|
+
success: true,
|
|
250
|
+
data: result,
|
|
251
|
+
stats: {
|
|
252
|
+
rows: result.length,
|
|
253
|
+
processingTime: Date.now() - startTime
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
} catch (error) {
|
|
258
|
+
return res.status(400).json({
|
|
259
|
+
success: false,
|
|
260
|
+
error: error.message
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Специализированный обработчик для JSON → CSV
|
|
267
|
+
*/
|
|
268
|
+
export async function jsonToCsvHandler(req, res) {
|
|
269
|
+
if (req.method !== 'POST') {
|
|
270
|
+
return res.status(405).json({
|
|
271
|
+
success: false,
|
|
272
|
+
error: 'Method not allowed',
|
|
273
|
+
allowed: ['POST']
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const startTime = Date.now();
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const jsonData = req.body;
|
|
281
|
+
|
|
282
|
+
if (!jsonData || (typeof jsonData !== 'object' && typeof jsonData !== 'string')) {
|
|
283
|
+
return res.status(400).json({
|
|
284
|
+
success: false,
|
|
285
|
+
error: 'JSON data is required'
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const {
|
|
290
|
+
delimiter = ',',
|
|
291
|
+
includeHeaders = 'true',
|
|
292
|
+
useFastPath = 'true'
|
|
293
|
+
} = req.query;
|
|
294
|
+
|
|
295
|
+
const data = typeof jsonData === 'string' ? JSON.parse(jsonData) : jsonData;
|
|
296
|
+
const result = await jsonToCsv(data, {
|
|
297
|
+
delimiter,
|
|
298
|
+
includeHeaders: includeHeaders === 'true',
|
|
299
|
+
useFastPath: useFastPath === 'true',
|
|
300
|
+
preventCsvInjection: true,
|
|
301
|
+
rfc4180Compliant: true
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
305
|
+
res.setHeader('Content-Disposition', 'attachment; filename="data.csv"');
|
|
306
|
+
|
|
307
|
+
return res.status(200).send(result);
|
|
308
|
+
|
|
309
|
+
} catch (error) {
|
|
310
|
+
return res.status(400).json({
|
|
311
|
+
success: false,
|
|
312
|
+
error: error.message
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Health check endpoint
|
|
319
|
+
*/
|
|
320
|
+
export async function healthCheckHandler(req, res) {
|
|
321
|
+
if (req.method !== 'GET') {
|
|
322
|
+
return res.status(405).json({
|
|
323
|
+
success: false,
|
|
324
|
+
error: 'Method not allowed',
|
|
325
|
+
allowed: ['GET']
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return res.status(200).json({
|
|
330
|
+
service: 'jtcsv-nextjs-api',
|
|
331
|
+
status: 'healthy',
|
|
332
|
+
version: '1.0.0',
|
|
333
|
+
timestamp: new Date().toISOString(),
|
|
334
|
+
features: {
|
|
335
|
+
csvToJson: true,
|
|
336
|
+
jsonToCsv: true,
|
|
337
|
+
fastPathEngine: true,
|
|
338
|
+
csvInjectionProtection: true,
|
|
339
|
+
streaming: true,
|
|
340
|
+
ndjson: true
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Утилита для создания кастомных API endpoints
|
|
347
|
+
*/
|
|
348
|
+
export function createJtcsvApiEndpoint(options = {}) {
|
|
349
|
+
const {
|
|
350
|
+
route = '/api/convert',
|
|
351
|
+
allowedMethods = ['POST'],
|
|
352
|
+
defaultFormat = 'json',
|
|
353
|
+
...defaultOptions
|
|
354
|
+
} = options;
|
|
355
|
+
|
|
356
|
+
return async function customHandler(req, res) {
|
|
357
|
+
// Проверяем разрешенные методы
|
|
358
|
+
if (!allowedMethods.includes(req.method)) {
|
|
359
|
+
return res.status(405).json({
|
|
360
|
+
success: false,
|
|
361
|
+
error: `Method ${req.method} not allowed`,
|
|
362
|
+
allowed: allowedMethods
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Здесь можно добавить кастомную логику
|
|
367
|
+
// Пока просто используем основной handler
|
|
368
|
+
return handler(req, res);
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// Браузерные специфичные функции для jtcsv
|
|
2
|
+
// Функции, которые работают только в браузере
|
|
3
|
+
|
|
4
|
+
import { jsonToCsv } from './json-to-csv-browser.js';
|
|
5
|
+
import { csvToJson } from './csv-to-json-browser.js';
|
|
6
|
+
import { ValidationError } from './errors-browser.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Скачивает JSON данные как CSV файл
|
|
10
|
+
*
|
|
11
|
+
* @param {Array<Object>} data - Массив объектов для конвертации
|
|
12
|
+
* @param {string} [filename='data.csv'] - Имя файла для скачивания
|
|
13
|
+
* @param {Object} [options] - Опции для jsonToCsv
|
|
14
|
+
* @returns {void}
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const data = [
|
|
18
|
+
* { id: 1, name: 'John' },
|
|
19
|
+
* { id: 2, name: 'Jane' }
|
|
20
|
+
* ];
|
|
21
|
+
* downloadAsCsv(data, 'users.csv', { delimiter: ',' });
|
|
22
|
+
*/
|
|
23
|
+
export function downloadAsCsv(data, filename = 'data.csv', options = {}) {
|
|
24
|
+
// Проверка что мы в браузере
|
|
25
|
+
if (typeof window === 'undefined') {
|
|
26
|
+
throw new ValidationError('downloadAsCsv() работает только в браузере. Используйте saveAsCsv() в Node.js');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Валидация имени файла
|
|
30
|
+
if (typeof filename !== 'string' || filename.trim() === '') {
|
|
31
|
+
throw new ValidationError('Filename must be a non-empty string');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Добавление расширения .csv если его нет
|
|
35
|
+
if (!filename.toLowerCase().endsWith('.csv')) {
|
|
36
|
+
filename += '.csv';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Конвертация в CSV
|
|
40
|
+
const csv = jsonToCsv(data, options);
|
|
41
|
+
|
|
42
|
+
// Создание Blob
|
|
43
|
+
const blob = new Blob([csv], {
|
|
44
|
+
type: 'text/csv;charset=utf-8;'
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Создание ссылки для скачивания
|
|
48
|
+
const link = document.createElement('a');
|
|
49
|
+
|
|
50
|
+
// Создание URL для Blob
|
|
51
|
+
const url = URL.createObjectURL(blob);
|
|
52
|
+
|
|
53
|
+
// Настройка ссылки
|
|
54
|
+
link.setAttribute('href', url);
|
|
55
|
+
link.setAttribute('download', filename);
|
|
56
|
+
link.style.visibility = 'hidden';
|
|
57
|
+
|
|
58
|
+
// Добавление в DOM и клик
|
|
59
|
+
document.body.appendChild(link);
|
|
60
|
+
link.click();
|
|
61
|
+
|
|
62
|
+
// Очистка
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
document.body.removeChild(link);
|
|
65
|
+
URL.revokeObjectURL(url);
|
|
66
|
+
}, 100);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Парсит CSV файл из input[type="file"] в JSON
|
|
71
|
+
*
|
|
72
|
+
* @param {File} file - File объект из input
|
|
73
|
+
* @param {Object} [options] - Опции для csvToJson
|
|
74
|
+
* @returns {Promise<Array<Object>>} Promise с JSON данными
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // HTML: <input type="file" id="csvFile" accept=".csv">
|
|
78
|
+
* const fileInput = document.getElementById('csvFile');
|
|
79
|
+
* const json = await parseCsvFile(fileInput.files[0], { delimiter: ',' });
|
|
80
|
+
*/
|
|
81
|
+
export async function parseCsvFile(file, options = {}) {
|
|
82
|
+
// Проверка что мы в браузере
|
|
83
|
+
if (typeof window === 'undefined') {
|
|
84
|
+
throw new ValidationError('parseCsvFile() работает только в браузере. Используйте readCsvAsJson() в Node.js');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Валидация файла
|
|
88
|
+
if (!(file instanceof File)) {
|
|
89
|
+
throw new ValidationError('Input must be a File object');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Проверка расширения файла
|
|
93
|
+
if (!file.name.toLowerCase().endsWith('.csv')) {
|
|
94
|
+
throw new ValidationError('File must have .csv extension');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Проверка размера файла (предупреждение для больших файлов)
|
|
98
|
+
const MAX_SIZE_WARNING = 50 * 1024 * 1024; // 50MB
|
|
99
|
+
if (file.size > MAX_SIZE_WARNING && process.env.NODE_ENV !== 'production') {
|
|
100
|
+
console.warn(
|
|
101
|
+
`⚠️ Warning: Processing large file (${(file.size / 1024 / 1024).toFixed(2)}MB).\n` +
|
|
102
|
+
'💡 Consider using Web Workers for better performance.\n' +
|
|
103
|
+
'🔧 Tip: Use parseCSVWithWorker() for files > 10MB.'
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
const reader = new FileReader();
|
|
109
|
+
|
|
110
|
+
reader.onload = function (event) {
|
|
111
|
+
try {
|
|
112
|
+
const csvText = event.target.result;
|
|
113
|
+
const json = csvToJson(csvText, options);
|
|
114
|
+
resolve(json);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
reject(error);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
reader.onerror = function () {
|
|
121
|
+
reject(new ValidationError('Ошибка чтения файла'));
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
reader.onabort = function () {
|
|
125
|
+
reject(new ValidationError('Чтение файла прервано'));
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Чтение как текст
|
|
129
|
+
reader.readAsText(file, 'UTF-8');
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Создает CSV файл из JSON данных (альтернатива downloadAsCsv)
|
|
135
|
+
* Возвращает Blob вместо автоматического скачивания
|
|
136
|
+
*
|
|
137
|
+
* @param {Array<Object>} data - Массив объектов
|
|
138
|
+
* @param {Object} [options] - Опции для jsonToCsv
|
|
139
|
+
* @returns {Blob} CSV Blob
|
|
140
|
+
*/
|
|
141
|
+
export function createCsvBlob(data, options = {}) {
|
|
142
|
+
const csv = jsonToCsv(data, options);
|
|
143
|
+
return new Blob([csv], {
|
|
144
|
+
type: 'text/csv;charset=utf-8;'
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Парсит CSV строку из Blob
|
|
150
|
+
*
|
|
151
|
+
* @param {Blob} blob - CSV Blob
|
|
152
|
+
* @param {Object} [options] - Опции для csvToJson
|
|
153
|
+
* @returns {Promise<Array<Object>>} Promise с JSON данными
|
|
154
|
+
*/
|
|
155
|
+
export async function parseCsvBlob(blob, options = {}) {
|
|
156
|
+
if (!(blob instanceof Blob)) {
|
|
157
|
+
throw new ValidationError('Input must be a Blob object');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
const reader = new FileReader();
|
|
162
|
+
|
|
163
|
+
reader.onload = function (event) {
|
|
164
|
+
try {
|
|
165
|
+
const csvText = event.target.result;
|
|
166
|
+
const json = csvToJson(csvText, options);
|
|
167
|
+
resolve(json);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
reject(error);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
reader.onerror = function () {
|
|
174
|
+
reject(new ValidationError('Ошибка чтения Blob'));
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
reader.readAsText(blob, 'UTF-8');
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Экспорт для Node.js совместимости
|
|
182
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
183
|
+
module.exports = {
|
|
184
|
+
downloadAsCsv,
|
|
185
|
+
parseCsvFile,
|
|
186
|
+
createCsvBlob,
|
|
187
|
+
parseCsvBlob
|
|
188
|
+
};
|
|
189
|
+
}
|