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.
Files changed (46) hide show
  1. package/README.md +252 -337
  2. package/bin/jtcsv.js +167 -85
  3. package/cli-tui.js +0 -0
  4. package/dist/jtcsv.cjs.js +1619 -0
  5. package/dist/jtcsv.cjs.js.map +1 -0
  6. package/dist/jtcsv.esm.js +1599 -0
  7. package/dist/jtcsv.esm.js.map +1 -0
  8. package/dist/jtcsv.umd.js +1625 -0
  9. package/dist/jtcsv.umd.js.map +1 -0
  10. package/examples/cli-tool.js +186 -0
  11. package/examples/express-api.js +167 -0
  12. package/examples/large-dataset-example.js +185 -0
  13. package/examples/plugin-excel-exporter.js +407 -0
  14. package/examples/simple-usage.js +280 -0
  15. package/examples/streaming-example.js +419 -0
  16. package/index.d.ts +4 -0
  17. package/json-save.js +1 -1
  18. package/package.json +128 -14
  19. package/plugins/README.md +373 -0
  20. package/plugins/express-middleware/README.md +306 -0
  21. package/plugins/express-middleware/example.js +136 -0
  22. package/plugins/express-middleware/index.d.ts +114 -0
  23. package/plugins/express-middleware/index.js +360 -0
  24. package/plugins/express-middleware/package.json +52 -0
  25. package/plugins/fastify-plugin/index.js +406 -0
  26. package/plugins/fastify-plugin/package.json +55 -0
  27. package/plugins/nextjs-api/README.md +452 -0
  28. package/plugins/nextjs-api/examples/ConverterComponent.jsx +386 -0
  29. package/plugins/nextjs-api/examples/api-convert.js +69 -0
  30. package/plugins/nextjs-api/index.js +388 -0
  31. package/plugins/nextjs-api/package.json +63 -0
  32. package/plugins/nextjs-api/route.js +372 -0
  33. package/src/browser/browser-functions.js +189 -0
  34. package/src/browser/csv-to-json-browser.js +442 -0
  35. package/src/browser/errors-browser.js +194 -0
  36. package/src/browser/index.js +79 -0
  37. package/src/browser/json-to-csv-browser.js +309 -0
  38. package/src/browser/workers/csv-parser.worker.js +359 -0
  39. package/src/browser/workers/worker-pool.js +467 -0
  40. package/src/core/plugin-system.js +472 -0
  41. package/src/engines/fast-path-engine-new.js +338 -0
  42. package/src/engines/fast-path-engine.js +347 -0
  43. package/src/formats/ndjson-parser.js +419 -0
  44. package/src/index-with-plugins.js +349 -0
  45. package/stream-csv-to-json.js +1 -1
  46. 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
+ }