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.
Files changed (140) hide show
  1. package/README.md +31 -1
  2. package/bin/jtcsv.js +891 -821
  3. package/bin/jtcsv.ts +2534 -0
  4. package/csv-to-json.js +168 -145
  5. package/dist/jtcsv-core.cjs.js +1407 -0
  6. package/dist/jtcsv-core.cjs.js.map +1 -0
  7. package/dist/jtcsv-core.esm.js +1379 -0
  8. package/dist/jtcsv-core.esm.js.map +1 -0
  9. package/dist/jtcsv-core.umd.js +1413 -0
  10. package/dist/jtcsv-core.umd.js.map +1 -0
  11. package/dist/jtcsv-full.cjs.js +1912 -0
  12. package/dist/jtcsv-full.cjs.js.map +1 -0
  13. package/dist/jtcsv-full.esm.js +1880 -0
  14. package/dist/jtcsv-full.esm.js.map +1 -0
  15. package/dist/jtcsv-full.umd.js +1918 -0
  16. package/dist/jtcsv-full.umd.js.map +1 -0
  17. package/dist/jtcsv-workers.esm.js +759 -0
  18. package/dist/jtcsv-workers.esm.js.map +1 -0
  19. package/dist/jtcsv-workers.umd.js +773 -0
  20. package/dist/jtcsv-workers.umd.js.map +1 -0
  21. package/dist/jtcsv.cjs.js +61 -19
  22. package/dist/jtcsv.cjs.js.map +1 -1
  23. package/dist/jtcsv.esm.js +61 -19
  24. package/dist/jtcsv.esm.js.map +1 -1
  25. package/dist/jtcsv.umd.js +61 -19
  26. package/dist/jtcsv.umd.js.map +1 -1
  27. package/errors.js +188 -2
  28. package/examples/advanced/conditional-transformations.js +446 -0
  29. package/examples/advanced/conditional-transformations.ts +446 -0
  30. package/examples/advanced/csv-parser.worker.js +89 -0
  31. package/examples/advanced/csv-parser.worker.ts +89 -0
  32. package/examples/advanced/nested-objects-example.js +306 -0
  33. package/examples/advanced/nested-objects-example.ts +306 -0
  34. package/examples/advanced/performance-optimization.js +504 -0
  35. package/examples/advanced/performance-optimization.ts +504 -0
  36. package/examples/advanced/run-demo-server.js +116 -0
  37. package/examples/advanced/run-demo-server.ts +116 -0
  38. package/examples/advanced/web-worker-usage.html +874 -0
  39. package/examples/async-multithreaded-example.ts +335 -0
  40. package/examples/cli-advanced-usage.md +288 -0
  41. package/examples/cli-batch-processing.ts +38 -0
  42. package/examples/cli-tool.js +0 -3
  43. package/examples/cli-tool.ts +183 -0
  44. package/examples/error-handling.js +21 -7
  45. package/examples/error-handling.ts +356 -0
  46. package/examples/express-api.js +0 -3
  47. package/examples/express-api.ts +164 -0
  48. package/examples/large-dataset-example.js +0 -3
  49. package/examples/large-dataset-example.ts +204 -0
  50. package/examples/ndjson-processing.js +1 -1
  51. package/examples/ndjson-processing.ts +456 -0
  52. package/examples/plugin-excel-exporter.js +3 -4
  53. package/examples/plugin-excel-exporter.ts +406 -0
  54. package/examples/react-integration.tsx +637 -0
  55. package/examples/schema-validation.ts +640 -0
  56. package/examples/simple-usage.js +254 -254
  57. package/examples/simple-usage.ts +194 -0
  58. package/examples/streaming-example.js +4 -5
  59. package/examples/streaming-example.ts +419 -0
  60. package/examples/web-workers-advanced.ts +28 -0
  61. package/index.d.ts +1 -3
  62. package/index.js +15 -1
  63. package/json-save.js +9 -3
  64. package/json-to-csv.js +168 -21
  65. package/package.json +69 -10
  66. package/plugins/express-middleware/README.md +21 -2
  67. package/plugins/express-middleware/example.js +3 -4
  68. package/plugins/express-middleware/example.ts +135 -0
  69. package/plugins/express-middleware/index.d.ts +1 -1
  70. package/plugins/express-middleware/index.js +270 -118
  71. package/plugins/express-middleware/index.ts +557 -0
  72. package/plugins/fastify-plugin/index.js +2 -4
  73. package/plugins/fastify-plugin/index.ts +443 -0
  74. package/plugins/hono/index.ts +226 -0
  75. package/plugins/nestjs/index.ts +201 -0
  76. package/plugins/nextjs-api/examples/ConverterComponent.tsx +386 -0
  77. package/plugins/nextjs-api/examples/api-convert.js +0 -2
  78. package/plugins/nextjs-api/examples/api-convert.ts +67 -0
  79. package/plugins/nextjs-api/index.tsx +339 -0
  80. package/plugins/nextjs-api/route.js +2 -3
  81. package/plugins/nextjs-api/route.ts +370 -0
  82. package/plugins/nuxt/index.ts +94 -0
  83. package/plugins/nuxt/runtime/composables/useJtcsv.ts +100 -0
  84. package/plugins/nuxt/runtime/plugin.ts +71 -0
  85. package/plugins/remix/index.js +1 -1
  86. package/plugins/remix/index.ts +260 -0
  87. package/plugins/sveltekit/index.js +1 -1
  88. package/plugins/sveltekit/index.ts +301 -0
  89. package/plugins/trpc/index.ts +267 -0
  90. package/src/browser/browser-functions.ts +402 -0
  91. package/src/browser/core.js +92 -0
  92. package/src/browser/core.ts +152 -0
  93. package/src/browser/csv-to-json-browser.d.ts +3 -0
  94. package/src/browser/csv-to-json-browser.js +36 -14
  95. package/src/browser/csv-to-json-browser.ts +264 -0
  96. package/src/browser/errors-browser.ts +303 -0
  97. package/src/browser/extensions/plugins.js +92 -0
  98. package/src/browser/extensions/plugins.ts +93 -0
  99. package/src/browser/extensions/workers.js +39 -0
  100. package/src/browser/extensions/workers.ts +39 -0
  101. package/src/browser/globals.d.ts +5 -0
  102. package/src/browser/index.ts +192 -0
  103. package/src/browser/json-to-csv-browser.d.ts +3 -0
  104. package/src/browser/json-to-csv-browser.js +13 -3
  105. package/src/browser/json-to-csv-browser.ts +262 -0
  106. package/src/browser/streams.js +12 -2
  107. package/src/browser/streams.ts +336 -0
  108. package/src/browser/workers/csv-parser.worker.ts +377 -0
  109. package/src/browser/workers/worker-pool.ts +548 -0
  110. package/src/core/delimiter-cache.js +22 -8
  111. package/src/core/delimiter-cache.ts +310 -0
  112. package/src/core/node-optimizations.ts +449 -0
  113. package/src/core/plugin-system.js +29 -11
  114. package/src/core/plugin-system.ts +400 -0
  115. package/src/core/transform-hooks.ts +558 -0
  116. package/src/engines/fast-path-engine-new.ts +347 -0
  117. package/src/engines/fast-path-engine.ts +854 -0
  118. package/src/errors.ts +72 -0
  119. package/src/formats/ndjson-parser.ts +469 -0
  120. package/src/formats/tsv-parser.ts +334 -0
  121. package/src/index-with-plugins.js +16 -9
  122. package/src/index-with-plugins.ts +395 -0
  123. package/src/types/index.ts +255 -0
  124. package/src/utils/bom-utils.js +259 -0
  125. package/src/utils/bom-utils.ts +373 -0
  126. package/src/utils/encoding-support.js +124 -0
  127. package/src/utils/encoding-support.ts +155 -0
  128. package/src/utils/schema-validator.js +19 -19
  129. package/src/utils/schema-validator.ts +819 -0
  130. package/src/utils/transform-loader.js +1 -1
  131. package/src/utils/transform-loader.ts +389 -0
  132. package/src/utils/zod-adapter.js +170 -0
  133. package/src/utils/zod-adapter.ts +280 -0
  134. package/src/web-server/index.js +10 -10
  135. package/src/web-server/index.ts +683 -0
  136. package/src/workers/csv-multithreaded.ts +310 -0
  137. package/src/workers/csv-parser.worker.ts +227 -0
  138. package/src/workers/worker-pool.ts +409 -0
  139. package/stream-csv-to-json.js +26 -8
  140. 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
- try {
69
- // Определяем формат входных данных
70
- let inputFormat = 'unknown';
71
- let inputData = req.body;
72
-
73
- if (autoDetect) {
74
- if (contentType.includes('application/json') ||
75
- (typeof req.body === 'object' && !Array.isArray(req.body))) {
76
- inputFormat = 'json';
77
- } else if (contentType.includes('text/csv') ||
78
- contentType.includes('text/plain') ||
79
- (typeof req.body === 'string' && req.body.includes(','))) {
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 (inputFormat === 'unknown') {
93
- return next();
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
- // Определяем желаемый формат вывода на основе Accept header
97
- let outputFormat = 'json';
98
- if (acceptHeader.includes('text/csv')) {
99
- outputFormat = 'csv';
100
- } else if (req.query.format === 'csv') {
101
- outputFormat = 'csv';
102
- } else if (req.body.format === 'csv') {
103
- outputFormat = 'csv';
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
- const conversionOptions = {
108
- delimiter,
109
- preventCsvInjection,
110
- rfc4180Compliant,
111
- useFastPath: enableFastPath,
112
- ...req.query,
113
- ...options.conversionOptions
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
- delete conversionOptions.maxSize;
118
- delete conversionOptions.autoDetect;
119
- delete conversionOptions.enableFastPath;
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
- let result;
122
- let stats = {
123
- inputSize: 0,
124
- outputSize: 0,
125
- processingTime: 0,
126
- conversion: `${inputFormat}→${outputFormat}`
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
- const startTime = Date.now();
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
- stats.processingTime = Date.now() - startTime;
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
- // Сохраняем результат в request object
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
- // Устанавливаем соответствующий Content-Type для ответа
165
- if (outputFormat === 'csv') {
166
- res.set('Content-Type', 'text/csv; charset=utf-8');
167
- } else {
168
- res.set('Content-Type', 'application/json; charset=utf-8');
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
- next();
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: error.message,
178
- code: error.code || 'CONVERSION_ERROR',
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
- errorResponse.stack = error.stack;
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(400).json(errorResponse);
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 result = await csvToJson(csvData, options);
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
- res.status(400).json({
377
+ const err = /** @type {Error} */ (error);
378
+ return res.status(400).json({
231
379
  success: false,
232
- error: error.message
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
- res.status(400).json({
415
+ const err = /** @type {Error} */ (error);
416
+ return res.status(400).json({
268
417
  success: false,
269
- error: error.message
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
- res.status(400).json({
467
+ const err = /** @type {Error} */ (error);
468
+ return res.status(400).json({
315
469
  success: false,
316
- error: error.message
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
+ };