jtcsv 2.2.7 → 3.0.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 +31 -1
- package/bin/jtcsv.js +891 -821
- package/bin/jtcsv.ts +2534 -0
- package/csv-to-json.js +168 -145
- package/dist/jtcsv-core.cjs.js +1407 -0
- package/dist/jtcsv-core.cjs.js.map +1 -0
- package/dist/jtcsv-core.esm.js +1379 -0
- package/dist/jtcsv-core.esm.js.map +1 -0
- package/dist/jtcsv-core.umd.js +1413 -0
- package/dist/jtcsv-core.umd.js.map +1 -0
- package/dist/jtcsv-full.cjs.js +1912 -0
- package/dist/jtcsv-full.cjs.js.map +1 -0
- package/dist/jtcsv-full.esm.js +1880 -0
- package/dist/jtcsv-full.esm.js.map +1 -0
- package/dist/jtcsv-full.umd.js +1918 -0
- package/dist/jtcsv-full.umd.js.map +1 -0
- package/dist/jtcsv-workers.esm.js +759 -0
- package/dist/jtcsv-workers.esm.js.map +1 -0
- package/dist/jtcsv-workers.umd.js +773 -0
- package/dist/jtcsv-workers.umd.js.map +1 -0
- package/dist/jtcsv.cjs.js +61 -19
- package/dist/jtcsv.cjs.js.map +1 -1
- package/dist/jtcsv.esm.js +61 -19
- package/dist/jtcsv.esm.js.map +1 -1
- package/dist/jtcsv.umd.js +61 -19
- package/dist/jtcsv.umd.js.map +1 -1
- package/errors.js +188 -2
- package/examples/advanced/conditional-transformations.js +446 -0
- package/examples/advanced/conditional-transformations.ts +446 -0
- package/examples/advanced/csv-parser.worker.js +89 -0
- package/examples/advanced/csv-parser.worker.ts +89 -0
- package/examples/advanced/nested-objects-example.js +306 -0
- package/examples/advanced/nested-objects-example.ts +306 -0
- package/examples/advanced/performance-optimization.js +504 -0
- package/examples/advanced/performance-optimization.ts +504 -0
- package/examples/advanced/run-demo-server.js +116 -0
- package/examples/advanced/run-demo-server.ts +116 -0
- package/examples/advanced/web-worker-usage.html +874 -0
- package/examples/async-multithreaded-example.ts +335 -0
- package/examples/cli-advanced-usage.md +288 -0
- package/examples/cli-batch-processing.ts +38 -0
- package/examples/cli-tool.js +0 -3
- package/examples/cli-tool.ts +183 -0
- package/examples/error-handling.js +21 -7
- package/examples/error-handling.ts +356 -0
- package/examples/express-api.js +0 -3
- package/examples/express-api.ts +164 -0
- package/examples/large-dataset-example.js +0 -3
- package/examples/large-dataset-example.ts +204 -0
- package/examples/ndjson-processing.js +1 -1
- package/examples/ndjson-processing.ts +456 -0
- package/examples/plugin-excel-exporter.js +3 -4
- package/examples/plugin-excel-exporter.ts +406 -0
- package/examples/react-integration.tsx +637 -0
- package/examples/schema-validation.ts +640 -0
- package/examples/simple-usage.js +254 -254
- package/examples/simple-usage.ts +194 -0
- package/examples/streaming-example.js +4 -5
- package/examples/streaming-example.ts +419 -0
- package/examples/web-workers-advanced.ts +28 -0
- package/index.d.ts +1 -3
- package/index.js +15 -1
- package/json-save.js +9 -3
- package/json-to-csv.js +168 -21
- package/package.json +69 -10
- package/plugins/express-middleware/README.md +21 -2
- package/plugins/express-middleware/example.js +3 -4
- package/plugins/express-middleware/example.ts +135 -0
- package/plugins/express-middleware/index.d.ts +1 -1
- package/plugins/express-middleware/index.js +270 -118
- package/plugins/express-middleware/index.ts +557 -0
- package/plugins/fastify-plugin/index.js +2 -4
- package/plugins/fastify-plugin/index.ts +443 -0
- package/plugins/hono/index.ts +226 -0
- package/plugins/nestjs/index.ts +201 -0
- package/plugins/nextjs-api/examples/ConverterComponent.tsx +386 -0
- package/plugins/nextjs-api/examples/api-convert.js +0 -2
- package/plugins/nextjs-api/examples/api-convert.ts +67 -0
- package/plugins/nextjs-api/index.tsx +339 -0
- package/plugins/nextjs-api/route.js +2 -3
- package/plugins/nextjs-api/route.ts +370 -0
- package/plugins/nuxt/index.ts +94 -0
- package/plugins/nuxt/runtime/composables/useJtcsv.ts +100 -0
- package/plugins/nuxt/runtime/plugin.ts +71 -0
- package/plugins/remix/index.js +1 -1
- package/plugins/remix/index.ts +260 -0
- package/plugins/sveltekit/index.js +1 -1
- package/plugins/sveltekit/index.ts +301 -0
- package/plugins/trpc/index.ts +267 -0
- package/src/browser/browser-functions.ts +402 -0
- package/src/browser/core.js +92 -0
- package/src/browser/core.ts +152 -0
- package/src/browser/csv-to-json-browser.d.ts +3 -0
- package/src/browser/csv-to-json-browser.js +36 -14
- package/src/browser/csv-to-json-browser.ts +264 -0
- package/src/browser/errors-browser.ts +303 -0
- package/src/browser/extensions/plugins.js +92 -0
- package/src/browser/extensions/plugins.ts +93 -0
- package/src/browser/extensions/workers.js +39 -0
- package/src/browser/extensions/workers.ts +39 -0
- package/src/browser/globals.d.ts +5 -0
- package/src/browser/index.ts +192 -0
- package/src/browser/json-to-csv-browser.d.ts +3 -0
- package/src/browser/json-to-csv-browser.js +13 -3
- package/src/browser/json-to-csv-browser.ts +262 -0
- package/src/browser/streams.js +12 -2
- package/src/browser/streams.ts +336 -0
- package/src/browser/workers/csv-parser.worker.ts +377 -0
- package/src/browser/workers/worker-pool.ts +548 -0
- package/src/core/delimiter-cache.js +22 -8
- package/src/core/delimiter-cache.ts +310 -0
- package/src/core/node-optimizations.ts +449 -0
- package/src/core/plugin-system.js +29 -11
- package/src/core/plugin-system.ts +400 -0
- package/src/core/transform-hooks.ts +558 -0
- package/src/engines/fast-path-engine-new.ts +347 -0
- package/src/engines/fast-path-engine.ts +854 -0
- package/src/errors.ts +72 -0
- package/src/formats/ndjson-parser.ts +469 -0
- package/src/formats/tsv-parser.ts +334 -0
- package/src/index-with-plugins.js +16 -9
- package/src/index-with-plugins.ts +395 -0
- package/src/types/index.ts +255 -0
- package/src/utils/bom-utils.js +259 -0
- package/src/utils/bom-utils.ts +373 -0
- package/src/utils/encoding-support.js +124 -0
- package/src/utils/encoding-support.ts +155 -0
- package/src/utils/schema-validator.js +19 -19
- package/src/utils/schema-validator.ts +819 -0
- package/src/utils/transform-loader.js +1 -1
- package/src/utils/transform-loader.ts +389 -0
- package/src/utils/zod-adapter.js +170 -0
- package/src/utils/zod-adapter.ts +280 -0
- package/src/web-server/index.js +10 -10
- package/src/web-server/index.ts +683 -0
- package/src/workers/csv-multithreaded.ts +310 -0
- package/src/workers/csv-parser.worker.ts +227 -0
- package/src/workers/worker-pool.ts +409 -0
- package/stream-csv-to-json.js +26 -8
- package/stream-json-to-csv.js +1 -0
|
@@ -1,18 +1,54 @@
|
|
|
1
|
+
/// <reference path="../../index.d.ts" />
|
|
1
2
|
/**
|
|
2
3
|
* Express middleware для JTCSV
|
|
3
4
|
* Автоматическая конвертация CSV/JSON в HTTP запросах
|
|
4
|
-
*
|
|
5
|
+
*
|
|
5
6
|
* @version 1.0.0
|
|
6
7
|
* @date 2026-01-23
|
|
7
8
|
*/
|
|
8
9
|
|
|
10
|
+
// @ts-ignore
|
|
9
11
|
const { csvToJson, jsonToCsv } = require('../../index.js');
|
|
10
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Преобразует строку размера (например, '500MB') в байты
|
|
15
|
+
* @param {string} sizeStr - Строка размера (например, '10MB', '1GB', '500KB')
|
|
16
|
+
* @returns {number} Размер в байтах
|
|
17
|
+
*/
|
|
18
|
+
function parseSizeToBytes(sizeStr) {
|
|
19
|
+
if (typeof sizeStr === 'number') {
|
|
20
|
+
return sizeStr;
|
|
21
|
+
}
|
|
22
|
+
if (typeof sizeStr !== 'string') {
|
|
23
|
+
return 10 * 1024 * 1024;
|
|
24
|
+
} // default 10MB
|
|
25
|
+
|
|
26
|
+
const match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)$/i);
|
|
27
|
+
if (!match) {
|
|
28
|
+
return 10 * 1024 * 1024;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const value = parseFloat(match[1]);
|
|
32
|
+
const unit = match[2].toUpperCase();
|
|
33
|
+
|
|
34
|
+
switch (unit) {
|
|
35
|
+
case 'B': return value;
|
|
36
|
+
case 'KB': return value * 1024;
|
|
37
|
+
case 'MB': return value * 1024 * 1024;
|
|
38
|
+
case 'GB': return value * 1024 * 1024 * 1024;
|
|
39
|
+
case 'TB': return value * 1024 * 1024 * 1024 * 1024;
|
|
40
|
+
default: return value * 1024 * 1024;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
11
44
|
/**
|
|
12
45
|
* Express middleware для обработки CSV/JSON конвертации
|
|
13
46
|
*
|
|
14
47
|
* @param {Object} options - Опции middleware
|
|
15
48
|
* @param {string} options.maxSize - Максимальный размер тела запроса (default: '10mb')
|
|
49
|
+
* @param {string} options.maxFileSize - Максимальный размер файла (например, '500MB', default: '500MB')
|
|
50
|
+
* @param {number} options.maxFieldSize - Максимальный размер поля в байтах (default: 1MB)
|
|
51
|
+
* @param {number} options.timeout - Таймаут обработки в миллисекундах (default: 300000 = 5 минут)
|
|
16
52
|
* @param {boolean} options.autoDetect - Автоматическое определение формата (default: true)
|
|
17
53
|
* @param {string} options.delimiter - Разделитель CSV (default: ',')
|
|
18
54
|
* @param {boolean} options.enableFastPath - Включить Fast-Path Engine (default: true)
|
|
@@ -31,11 +67,23 @@ const { csvToJson, jsonToCsv } = require('../../index.js');
|
|
|
31
67
|
* // С кастомными опциями
|
|
32
68
|
* app.use(jtcsvMiddleware({
|
|
33
69
|
* maxSize: '50mb',
|
|
70
|
+
* maxFileSize: '1GB',
|
|
71
|
+
* maxFieldSize: 5 * 1024 * 1024, // 5MB
|
|
72
|
+
* timeout: 600000, // 10 минут
|
|
34
73
|
* delimiter: ';',
|
|
35
74
|
* enableFastPath: true
|
|
36
75
|
* }));
|
|
37
76
|
*
|
|
38
77
|
* @example
|
|
78
|
+
* // Использование с rate limiting
|
|
79
|
+
* import rateLimit from 'express-rate-limit';
|
|
80
|
+
* const importLimiter = rateLimit({
|
|
81
|
+
* windowMs: 15 * 60 * 1000,
|
|
82
|
+
* max: 10
|
|
83
|
+
* });
|
|
84
|
+
* app.post('/api/import', importLimiter, jtcsvMiddleware());
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
39
87
|
* // Использование в роуте
|
|
40
88
|
* app.post('/api/convert', (req, res) => {
|
|
41
89
|
* // Конвертированные данные доступны в req.converted
|
|
@@ -46,142 +94,235 @@ const { csvToJson, jsonToCsv } = require('../../index.js');
|
|
|
46
94
|
* });
|
|
47
95
|
* });
|
|
48
96
|
*/
|
|
97
|
+
/**
|
|
98
|
+
* @typedef {Object} JtcsvExpressMiddlewareOptions
|
|
99
|
+
* @property {string} [maxSize='10mb'] - Максимальный размер тела запроса
|
|
100
|
+
* @property {string} [maxFileSize='500MB'] - Максимальный размер файла
|
|
101
|
+
* @property {number} [maxFieldSize=1048576] - Максимальный размер поля в байтах (1MB)
|
|
102
|
+
* @property {number} [timeout=300000] - Таймаут обработки в миллисекундах (5 минут)
|
|
103
|
+
* @property {boolean} [autoDetect=true] - Автоматическое определение формата
|
|
104
|
+
* @property {string} [delimiter=','] - Разделитель CSV
|
|
105
|
+
* @property {boolean} [enableFastPath=true] - Включить Fast-Path Engine
|
|
106
|
+
* @property {boolean} [preventCsvInjection=true] - Защита от CSV инъекций
|
|
107
|
+
* @property {boolean} [rfc4180Compliant=true] - Соблюдение RFC4180
|
|
108
|
+
* @property {Object} [conversionOptions] - Дополнительные опции конвертации
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Express middleware для обработки CSV/JSON конвертации
|
|
113
|
+
*
|
|
114
|
+
* @param {JtcsvExpressMiddlewareOptions} options - Опции middleware
|
|
115
|
+
* @returns {Function} Express middleware
|
|
116
|
+
*/
|
|
49
117
|
function jtcsvExpressMiddleware(options = {}) {
|
|
50
118
|
const {
|
|
51
119
|
maxSize = '10mb',
|
|
120
|
+
maxFileSize = '500MB',
|
|
121
|
+
maxFieldSize = 1024 * 1024, // 1MB
|
|
122
|
+
timeout = 300000, // 5 minutes
|
|
52
123
|
autoDetect = true,
|
|
53
124
|
delimiter = ',',
|
|
54
125
|
enableFastPath = true,
|
|
55
126
|
preventCsvInjection = true,
|
|
56
|
-
rfc4180Compliant = true
|
|
127
|
+
rfc4180Compliant = true,
|
|
128
|
+
conversionOptions = {}
|
|
57
129
|
} = options;
|
|
58
130
|
|
|
59
|
-
return async (req, res, next) => {
|
|
131
|
+
return async (/** @type {import('express').Request} */ req, /** @type {import('express').Response} */ res, /** @type {import('express').NextFunction} */ next) => {
|
|
132
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
133
|
+
console.log('[jtcsv-middleware] Request received:', req.method, req.url, req.headers['content-type']);
|
|
134
|
+
}
|
|
60
135
|
// Пропускаем запросы без тела
|
|
61
136
|
if (!req.body || (typeof req.body !== 'string' && typeof req.body !== 'object')) {
|
|
137
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
138
|
+
console.log('[jtcsv-middleware] No body, skipping');
|
|
139
|
+
}
|
|
62
140
|
return next();
|
|
63
141
|
}
|
|
64
142
|
|
|
143
|
+
// Проверка размера файла
|
|
144
|
+
const contentLength = req.get('content-length');
|
|
145
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
146
|
+
console.log(`[jtcsv-middleware] Content-Length: "${contentLength}"`);
|
|
147
|
+
}
|
|
148
|
+
if (contentLength && contentLength.trim() !== '') {
|
|
149
|
+
const maxBytes = parseSizeToBytes(maxFileSize);
|
|
150
|
+
const contentLengthInt = parseInt(contentLength, 10);
|
|
151
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
152
|
+
console.log(`[jtcsv-middleware] maxBytes: ${maxBytes}, contentLengthInt: ${contentLengthInt}`);
|
|
153
|
+
}
|
|
154
|
+
if (contentLengthInt > maxBytes) {
|
|
155
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
156
|
+
console.log(`[jtcsv-middleware] File size limit exceeded: ${contentLengthInt} > ${maxBytes}`);
|
|
157
|
+
}
|
|
158
|
+
return res.status(413).json({
|
|
159
|
+
success: false,
|
|
160
|
+
error: `File size exceeds limit of ${maxFileSize}`,
|
|
161
|
+
code: 'FILE_SIZE_LIMIT_EXCEEDED'
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
65
166
|
const contentType = req.get('content-type') || '';
|
|
66
167
|
const acceptHeader = req.get('accept') || 'application/json';
|
|
67
168
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
inputFormat = 'csv';
|
|
81
|
-
}
|
|
82
|
-
} else {
|
|
83
|
-
// Ручное определение на основе content-type
|
|
84
|
-
if (contentType.includes('application/json')) {
|
|
85
|
-
inputFormat = 'json';
|
|
86
|
-
} else if (contentType.includes('text/csv')) {
|
|
87
|
-
inputFormat = 'csv';
|
|
88
|
-
}
|
|
169
|
+
// Определяем формат входных данных заранее
|
|
170
|
+
let inputFormat = 'unknown';
|
|
171
|
+
const inputData = req.body;
|
|
172
|
+
|
|
173
|
+
if (autoDetect) {
|
|
174
|
+
if (contentType.includes('application/json') ||
|
|
175
|
+
(req.body !== null && typeof req.body === 'object' && !Array.isArray(req.body))) {
|
|
176
|
+
inputFormat = 'json';
|
|
177
|
+
} else if (contentType.includes('text/csv') ||
|
|
178
|
+
contentType.includes('text/plain') ||
|
|
179
|
+
(typeof req.body === 'string' && req.body.includes(','))) {
|
|
180
|
+
inputFormat = 'csv';
|
|
89
181
|
}
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
if (
|
|
93
|
-
|
|
182
|
+
} else {
|
|
183
|
+
// Ручное определение на основе content-type
|
|
184
|
+
if (contentType.includes('application/json')) {
|
|
185
|
+
inputFormat = 'json';
|
|
186
|
+
} else if (contentType.includes('text/csv')) {
|
|
187
|
+
inputFormat = 'csv';
|
|
94
188
|
}
|
|
189
|
+
}
|
|
95
190
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
191
|
+
// Если формат не определен, пропускаем
|
|
192
|
+
if (inputFormat === 'unknown') {
|
|
193
|
+
return next();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Установка таймаута
|
|
197
|
+
let timeoutId;
|
|
198
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
199
|
+
timeoutId = setTimeout(() => {
|
|
200
|
+
reject(new Error(`Request processing timeout (${timeout}ms)`));
|
|
201
|
+
}, timeout);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
// Обернем основную логику в Promise.race с таймаутом
|
|
206
|
+
const processingPromise = (async () => {
|
|
105
207
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
208
|
+
// Определяем желаемый формат вывода на основе Accept header
|
|
209
|
+
let outputFormat = 'json';
|
|
210
|
+
if (acceptHeader.includes('text/csv')) {
|
|
211
|
+
outputFormat = 'csv';
|
|
212
|
+
} else if (req.query.format === 'csv') {
|
|
213
|
+
outputFormat = 'csv';
|
|
214
|
+
} else if (req.body.format === 'csv') {
|
|
215
|
+
outputFormat = 'csv';
|
|
216
|
+
}
|
|
115
217
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
218
|
+
// Опции конвертации
|
|
219
|
+
const conversionOptions = {
|
|
220
|
+
delimiter,
|
|
221
|
+
preventCsvInjection,
|
|
222
|
+
rfc4180Compliant,
|
|
223
|
+
useFastPath: enableFastPath,
|
|
224
|
+
maxFieldSize,
|
|
225
|
+
...req.query,
|
|
226
|
+
...options.conversionOptions
|
|
227
|
+
};
|
|
120
228
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
229
|
+
// Удаляем параметры, которые не относятся к конвертации
|
|
230
|
+
// @ts-ignore
|
|
231
|
+
delete conversionOptions.maxSize;
|
|
232
|
+
// @ts-ignore
|
|
233
|
+
delete conversionOptions.maxFileSize;
|
|
234
|
+
// @ts-ignore
|
|
235
|
+
delete conversionOptions.maxFieldSize;
|
|
236
|
+
// @ts-ignore
|
|
237
|
+
delete conversionOptions.timeout;
|
|
238
|
+
// @ts-ignore
|
|
239
|
+
delete conversionOptions.autoDetect;
|
|
240
|
+
// @ts-ignore
|
|
241
|
+
delete conversionOptions.enableFastPath;
|
|
128
242
|
|
|
129
|
-
|
|
243
|
+
let result;
|
|
244
|
+
const stats = {
|
|
245
|
+
inputSize: 0,
|
|
246
|
+
outputSize: 0,
|
|
247
|
+
processingTime: 0,
|
|
248
|
+
conversion: `${inputFormat}→${outputFormat}`
|
|
249
|
+
};
|
|
130
250
|
|
|
131
|
-
|
|
132
|
-
if (inputFormat === 'json' && outputFormat === 'csv') {
|
|
133
|
-
const jsonData = typeof inputData === 'string' ? JSON.parse(inputData) : inputData;
|
|
134
|
-
stats.inputSize = Buffer.byteLength(JSON.stringify(jsonData));
|
|
135
|
-
|
|
136
|
-
result = await jsonToCsv(jsonData, conversionOptions);
|
|
137
|
-
stats.outputSize = Buffer.byteLength(result);
|
|
138
|
-
|
|
139
|
-
} else if (inputFormat === 'csv' && outputFormat === 'json') {
|
|
140
|
-
const csvData = typeof inputData === 'string' ? inputData : String(inputData);
|
|
141
|
-
stats.inputSize = Buffer.byteLength(csvData);
|
|
142
|
-
|
|
143
|
-
result = await csvToJson(csvData, conversionOptions);
|
|
144
|
-
stats.outputSize = Buffer.byteLength(JSON.stringify(result));
|
|
145
|
-
|
|
146
|
-
} else {
|
|
147
|
-
// Нет необходимости в конвертации
|
|
148
|
-
result = inputData;
|
|
149
|
-
stats.conversion = 'none';
|
|
150
|
-
}
|
|
251
|
+
const startTime = Date.now();
|
|
151
252
|
|
|
152
|
-
|
|
253
|
+
// Выполняем конвертацию
|
|
254
|
+
if (inputFormat === 'json' && outputFormat === 'csv') {
|
|
255
|
+
const jsonData = typeof inputData === 'string' ? JSON.parse(inputData) : inputData;
|
|
256
|
+
stats.inputSize = Buffer.byteLength(JSON.stringify(jsonData));
|
|
257
|
+
|
|
258
|
+
result = await jsonToCsv(jsonData, conversionOptions);
|
|
259
|
+
stats.outputSize = Buffer.byteLength(result);
|
|
260
|
+
|
|
261
|
+
} else if (inputFormat === 'csv' && outputFormat === 'json') {
|
|
262
|
+
const csvData = typeof inputData === 'string' ? inputData : String(inputData);
|
|
263
|
+
stats.inputSize = Buffer.byteLength(csvData);
|
|
264
|
+
|
|
265
|
+
result = await csvToJson(csvData, conversionOptions);
|
|
266
|
+
stats.outputSize = Buffer.byteLength(JSON.stringify(result));
|
|
267
|
+
|
|
268
|
+
} else {
|
|
269
|
+
// Нет необходимости в конвертации
|
|
270
|
+
result = inputData;
|
|
271
|
+
stats.conversion = 'none';
|
|
272
|
+
}
|
|
153
273
|
|
|
154
|
-
|
|
155
|
-
req.converted = {
|
|
156
|
-
data: result,
|
|
157
|
-
format: outputFormat,
|
|
158
|
-
inputFormat,
|
|
159
|
-
outputFormat,
|
|
160
|
-
stats,
|
|
161
|
-
options: conversionOptions
|
|
162
|
-
};
|
|
274
|
+
stats.processingTime = Date.now() - startTime;
|
|
163
275
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
276
|
+
// Сохраняем результат в request object
|
|
277
|
+
// @ts-ignore
|
|
278
|
+
req.converted = {
|
|
279
|
+
data: result,
|
|
280
|
+
format: outputFormat,
|
|
281
|
+
inputFormat,
|
|
282
|
+
outputFormat,
|
|
283
|
+
stats,
|
|
284
|
+
options: conversionOptions
|
|
285
|
+
};
|
|
170
286
|
|
|
171
|
-
|
|
287
|
+
// Устанавливаем соответствующий Content-Type для ответа
|
|
288
|
+
if (outputFormat === 'csv') {
|
|
289
|
+
res.set('Content-Type', 'text/csv; charset=utf-8');
|
|
290
|
+
} else {
|
|
291
|
+
res.set('Content-Type', 'application/json; charset=utf-8');
|
|
292
|
+
}
|
|
172
293
|
|
|
294
|
+
next();
|
|
295
|
+
})();
|
|
296
|
+
|
|
297
|
+
// Ждем либо обработку, либо таймаут
|
|
298
|
+
await Promise.race([processingPromise, timeoutPromise]);
|
|
173
299
|
} catch (error) {
|
|
174
300
|
// Обработка ошибок конвертации
|
|
301
|
+
const err = /** @type {Error & { code?: string }} */ (error);
|
|
302
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
303
|
+
console.log('[jtcsv-middleware] Conversion error:', err.message, err.stack?.split('\n')[0]);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Определяем статус код на основе типа ошибки
|
|
307
|
+
let statusCode = 400;
|
|
308
|
+
if (err.message.includes('timeout') || err.message.includes('Timeout')) {
|
|
309
|
+
statusCode = 408; // Request Timeout
|
|
310
|
+
} else if (err.message.includes('File size exceeds limit')) {
|
|
311
|
+
statusCode = 413; // Payload Too Large
|
|
312
|
+
}
|
|
313
|
+
|
|
175
314
|
const errorResponse = {
|
|
176
315
|
success: false,
|
|
177
|
-
error:
|
|
178
|
-
code:
|
|
316
|
+
error: err.message,
|
|
317
|
+
code: err.code || 'CONVERSION_ERROR',
|
|
179
318
|
timestamp: new Date().toISOString()
|
|
180
319
|
};
|
|
181
320
|
|
|
182
321
|
// Добавляем дополнительную информацию для отладки
|
|
183
322
|
if (process.env.NODE_ENV === 'development') {
|
|
184
|
-
|
|
323
|
+
// @ts-ignore
|
|
324
|
+
errorResponse.stack = err.stack;
|
|
325
|
+
// @ts-ignore
|
|
185
326
|
errorResponse.details = {
|
|
186
327
|
contentType: req.get('content-type'),
|
|
187
328
|
contentLength: req.get('content-length'),
|
|
@@ -190,7 +331,12 @@ function jtcsvExpressMiddleware(options = {}) {
|
|
|
190
331
|
};
|
|
191
332
|
}
|
|
192
333
|
|
|
193
|
-
res.status(
|
|
334
|
+
res.status(statusCode).json(errorResponse);
|
|
335
|
+
} finally {
|
|
336
|
+
// Очищаем таймаут
|
|
337
|
+
if (timeoutId) {
|
|
338
|
+
clearTimeout(timeoutId);
|
|
339
|
+
}
|
|
194
340
|
}
|
|
195
341
|
};
|
|
196
342
|
}
|
|
@@ -205,7 +351,7 @@ function jtcsvExpressMiddleware(options = {}) {
|
|
|
205
351
|
* app.post('/api/csv-to-json', jtcsvCsvToJsonRoute());
|
|
206
352
|
*/
|
|
207
353
|
function jtcsvCsvToJsonRoute(options = {}) {
|
|
208
|
-
return async (req, res) => {
|
|
354
|
+
return async (/** @type {import('express').Request} */ req, /** @type {import('express').Response} */ res) => {
|
|
209
355
|
try {
|
|
210
356
|
const csvData = req.body;
|
|
211
357
|
|
|
@@ -216,20 +362,22 @@ function jtcsvCsvToJsonRoute(options = {}) {
|
|
|
216
362
|
});
|
|
217
363
|
}
|
|
218
364
|
|
|
219
|
-
const
|
|
365
|
+
const csvString = Buffer.isBuffer(csvData) ? csvData.toString() : csvData;
|
|
366
|
+
const result = await csvToJson(csvString, options);
|
|
220
367
|
|
|
221
|
-
res.json({
|
|
368
|
+
return res.json({
|
|
222
369
|
success: true,
|
|
223
370
|
data: result,
|
|
224
371
|
stats: {
|
|
225
372
|
rows: result.length,
|
|
226
|
-
processingTime: Date.now() - req.startTime
|
|
373
|
+
processingTime: Date.now() - (/** @type {any} */ (req).startTime || Date.now())
|
|
227
374
|
}
|
|
228
375
|
});
|
|
229
376
|
} catch (error) {
|
|
230
|
-
|
|
377
|
+
const err = /** @type {Error} */ (error);
|
|
378
|
+
return res.status(400).json({
|
|
231
379
|
success: false,
|
|
232
|
-
error:
|
|
380
|
+
error: err.message
|
|
233
381
|
});
|
|
234
382
|
}
|
|
235
383
|
};
|
|
@@ -245,7 +393,7 @@ function jtcsvCsvToJsonRoute(options = {}) {
|
|
|
245
393
|
* app.post('/api/json-to-csv', jtcsvJsonToCsvRoute());
|
|
246
394
|
*/
|
|
247
395
|
function jtcsvJsonToCsvRoute(options = {}) {
|
|
248
|
-
return async (req, res) => {
|
|
396
|
+
return async (/** @type {import('express').Request} */ req, /** @type {import('express').Response} */ res) => {
|
|
249
397
|
try {
|
|
250
398
|
const jsonData = req.body;
|
|
251
399
|
|
|
@@ -262,11 +410,12 @@ function jtcsvJsonToCsvRoute(options = {}) {
|
|
|
262
410
|
res.set('Content-Type', 'text/csv; charset=utf-8');
|
|
263
411
|
res.set('Content-Disposition', 'attachment; filename="data.csv"');
|
|
264
412
|
|
|
265
|
-
res.send(result);
|
|
413
|
+
return res.send(result);
|
|
266
414
|
} catch (error) {
|
|
267
|
-
|
|
415
|
+
const err = /** @type {Error} */ (error);
|
|
416
|
+
return res.status(400).json({
|
|
268
417
|
success: false,
|
|
269
|
-
error:
|
|
418
|
+
error: err.message
|
|
270
419
|
});
|
|
271
420
|
}
|
|
272
421
|
};
|
|
@@ -284,8 +433,9 @@ function jtcsvJsonToCsvRoute(options = {}) {
|
|
|
284
433
|
* app.post('/api/upload-csv', upload.single('file'), jtcsvUploadCsvRoute());
|
|
285
434
|
*/
|
|
286
435
|
function jtcsvUploadCsvRoute(options = {}) {
|
|
287
|
-
return async (req, res) => {
|
|
436
|
+
return async (/** @type {import('express').Request} */ req, /** @type {import('express').Response} */ res) => {
|
|
288
437
|
try {
|
|
438
|
+
// @ts-ignore
|
|
289
439
|
if (!req.file) {
|
|
290
440
|
return res.status(400).json({
|
|
291
441
|
success: false,
|
|
@@ -294,26 +444,30 @@ function jtcsvUploadCsvRoute(options = {}) {
|
|
|
294
444
|
}
|
|
295
445
|
|
|
296
446
|
const fs = require('fs').promises;
|
|
447
|
+
// @ts-ignore
|
|
297
448
|
const csvData = await fs.readFile(req.file.path, 'utf8');
|
|
298
449
|
|
|
299
450
|
const result = await csvToJson(csvData, options);
|
|
300
451
|
|
|
301
452
|
// Очищаем временный файл
|
|
453
|
+
// @ts-ignore
|
|
302
454
|
await fs.unlink(req.file.path);
|
|
303
455
|
|
|
304
|
-
res.json({
|
|
456
|
+
return res.json({
|
|
305
457
|
success: true,
|
|
306
458
|
data: result,
|
|
307
459
|
stats: {
|
|
308
460
|
rows: result.length,
|
|
461
|
+
// @ts-ignore
|
|
309
462
|
fileSize: req.file.size,
|
|
310
|
-
processingTime: Date.now() - req.startTime
|
|
463
|
+
processingTime: Date.now() - (/** @type {any} */ (req).startTime || Date.now())
|
|
311
464
|
}
|
|
312
465
|
});
|
|
313
466
|
} catch (error) {
|
|
314
|
-
|
|
467
|
+
const err = /** @type {Error} */ (error);
|
|
468
|
+
return res.status(400).json({
|
|
315
469
|
success: false,
|
|
316
|
-
error:
|
|
470
|
+
error: err.message
|
|
317
471
|
});
|
|
318
472
|
}
|
|
319
473
|
};
|
|
@@ -328,7 +482,7 @@ function jtcsvUploadCsvRoute(options = {}) {
|
|
|
328
482
|
* app.get('/api/health', jtcsvHealthCheck());
|
|
329
483
|
*/
|
|
330
484
|
function jtcsvHealthCheck() {
|
|
331
|
-
return (req, res) => {
|
|
485
|
+
return (/** @type {import('express').Request} */ req, /** @type {import('express').Response} */ res) => {
|
|
332
486
|
res.json({
|
|
333
487
|
service: 'jtcsv-express-middleware',
|
|
334
488
|
status: 'healthy',
|
|
@@ -355,6 +509,4 @@ module.exports = {
|
|
|
355
509
|
// Aliases для удобства
|
|
356
510
|
jtcsvMiddleware: jtcsvExpressMiddleware,
|
|
357
511
|
createMiddleware: jtcsvExpressMiddleware
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
|
|
512
|
+
};
|