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
@@ -0,0 +1,557 @@
1
+ /**
2
+ * Express middleware для JTCSV
3
+ * Автоматическая конвертация CSV/JSON в HTTP запросах
4
+ *
5
+ * @version 2.0.0 - TypeScript версия с асинхронной поддержкой
6
+ * @date 2026-01-29
7
+ */
8
+
9
+ import { Request, Response, NextFunction } from 'express';
10
+ import { csvToJson, csvToJsonAsync, jsonToCsv, jsonToCsvAsync } from '../../index-core';
11
+ import { JtcsvError, ValidationError, SecurityError, FileSystemError } from '../../errors';
12
+
13
+ /**
14
+ * Интерфейс опций Express middleware
15
+ */
16
+ export interface JtcsvExpressMiddlewareOptions {
17
+ /** Максимальный размер тела запроса (default: '10mb') */
18
+ maxSize?: string;
19
+ /** Максимальный размер файла (например, '500MB', default: '500MB') */
20
+ maxFileSize?: string;
21
+ /** Максимальный размер поля в байтах (default: 1MB) */
22
+ maxFieldSize?: number;
23
+ /** Таймаут обработки в миллисекундах (default: 300000 = 5 минут) */
24
+ timeout?: number;
25
+ /** Автоматическое определение формата (default: true) */
26
+ autoDetect?: boolean;
27
+ /** Разделитель CSV (default: ',') */
28
+ delimiter?: string;
29
+ /** Включить Fast-Path Engine (default: true) */
30
+ enableFastPath?: boolean;
31
+ /** Защита от CSV инъекций (default: true) */
32
+ preventCsvInjection?: boolean;
33
+ /** Соблюдение RFC4180 (default: true) */
34
+ rfc4180Compliant?: boolean;
35
+ /** Дополнительные опции конвертации */
36
+ conversionOptions?: Record<string, any>;
37
+ /** Использовать асинхронные версии функций (default: true) */
38
+ useAsync?: boolean;
39
+ /** Использовать многопоточную обработку для больших данных (default: auto-detect) */
40
+ useWorkers?: boolean;
41
+ /** Количество worker'ов для многопоточной обработки (default: CPU cores - 1) */
42
+ workerCount?: number;
43
+ }
44
+
45
+ /**
46
+ * Интерфейс конвертированных данных в request object
47
+ */
48
+ export interface ConvertedData {
49
+ /** Конвертированные данные */
50
+ data: any;
51
+ /** Формат вывода */
52
+ format: 'json' | 'csv';
53
+ /** Формат ввода */
54
+ inputFormat: 'json' | 'csv' | 'unknown';
55
+ /** Формат вывода */
56
+ outputFormat: 'json' | 'csv';
57
+ /** Статистика обработки */
58
+ stats: {
59
+ inputSize: number;
60
+ outputSize: number;
61
+ processingTime: number;
62
+ conversion: string;
63
+ workerCount?: number;
64
+ };
65
+ /** Опции конвертации */
66
+ options: Record<string, any>;
67
+ }
68
+
69
+ /**
70
+ * Расширенный интерфейс Request с конвертированными данными
71
+ */
72
+ declare global {
73
+ namespace Express {
74
+ interface Request {
75
+ /** Конвертированные данные */
76
+ converted?: ConvertedData;
77
+ /** Время начала обработки */
78
+ startTime?: number;
79
+ }
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Преобразует строку размера (например, '500MB') в байты
85
+ * @param sizeStr - Строка размера (например, '10MB', '1GB', '500KB')
86
+ * @returns Размер в байтах
87
+ */
88
+ function parseSizeToBytes(sizeStr: string | number): number {
89
+ if (typeof sizeStr === 'number') {
90
+ return sizeStr;
91
+ }
92
+ if (typeof sizeStr !== 'string') {
93
+ return 10 * 1024 * 1024; // default 10MB
94
+ }
95
+
96
+ const match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)$/i);
97
+ if (!match) {
98
+ return 10 * 1024 * 1024;
99
+ }
100
+
101
+ const value = parseFloat(match[1]);
102
+ const unit = match[2].toUpperCase();
103
+
104
+ switch (unit) {
105
+ case 'B': return value;
106
+ case 'KB': return value * 1024;
107
+ case 'MB': return value * 1024 * 1024;
108
+ case 'GB': return value * 1024 * 1024 * 1024;
109
+ case 'TB': return value * 1024 * 1024 * 1024 * 1024;
110
+ default: return value * 1024 * 1024;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Express middleware для обработки CSV/JSON конвертации
116
+ *
117
+ * @param options - Опции middleware
118
+ * @returns Express middleware
119
+ *
120
+ * @example
121
+ * // Базовое использование
122
+ * const app = express();
123
+ * app.use(express.json());
124
+ * app.use(express.text({ type: 'text/csv' }));
125
+ * app.use(jtcsvExpressMiddleware());
126
+ *
127
+ * @example
128
+ * // С кастомными опциями
129
+ * app.use(jtcsvExpressMiddleware({
130
+ * maxSize: '50mb',
131
+ * maxFileSize: '1GB',
132
+ * maxFieldSize: 5 * 1024 * 1024, // 5MB
133
+ * timeout: 600000, // 10 минут
134
+ * delimiter: ';',
135
+ * enableFastPath: true,
136
+ * useAsync: true, // Использовать асинхронные функции
137
+ * useWorkers: true // Включить многопоточную обработку
138
+ * }));
139
+ */
140
+ export function jtcsvExpressMiddleware(options: JtcsvExpressMiddlewareOptions = {}): (req: Request, res: Response, next: NextFunction) => Promise<void> {
141
+ const {
142
+ maxSize = '10mb',
143
+ maxFileSize = '500MB',
144
+ maxFieldSize = 1024 * 1024, // 1MB
145
+ timeout = 300000, // 5 minutes
146
+ autoDetect = true,
147
+ delimiter = ',',
148
+ enableFastPath = true,
149
+ preventCsvInjection = true,
150
+ rfc4180Compliant = true,
151
+ useAsync = true,
152
+ useWorkers,
153
+ workerCount,
154
+ conversionOptions = {}
155
+ } = options;
156
+
157
+ return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
158
+ if (process.env.NODE_ENV !== 'test') {
159
+ console.log('[jtcsv-middleware] Request received:', req.method, req.url, req.headers['content-type']);
160
+ }
161
+
162
+ // Устанавливаем время начала обработки
163
+ req.startTime = Date.now();
164
+
165
+ // Пропускаем запросы без тела
166
+ if (!req.body || (typeof req.body !== 'string' && typeof req.body !== 'object')) {
167
+ if (process.env.NODE_ENV !== 'test') {
168
+ console.log('[jtcsv-middleware] No body, skipping');
169
+ }
170
+ return next();
171
+ }
172
+
173
+ // Проверка размера файла
174
+ const contentLength = req.get('content-length');
175
+ if (process.env.NODE_ENV !== 'test') {
176
+ console.log(`[jtcsv-middleware] Content-Length: "${contentLength}"`);
177
+ }
178
+ if (contentLength && contentLength.trim() !== '') {
179
+ const maxBytes = parseSizeToBytes(maxFileSize);
180
+ const contentLengthInt = parseInt(contentLength, 10);
181
+ if (process.env.NODE_ENV !== 'test') {
182
+ console.log(`[jtcsv-middleware] maxBytes: ${maxBytes}, contentLengthInt: ${contentLengthInt}`);
183
+ }
184
+ if (contentLengthInt > maxBytes) {
185
+ if (process.env.NODE_ENV !== 'test') {
186
+ console.log(`[jtcsv-middleware] File size limit exceeded: ${contentLengthInt} > ${maxBytes}`);
187
+ }
188
+ res.status(413).json({
189
+ success: false,
190
+ error: `File size exceeds limit of ${maxFileSize}`,
191
+ code: 'FILE_SIZE_LIMIT_EXCEEDED'
192
+ });
193
+ return;
194
+ }
195
+ }
196
+
197
+ const contentType = req.get('content-type') || '';
198
+ const acceptHeader = req.get('accept') || 'application/json';
199
+
200
+ // Определяем формат входных данных заранее
201
+ let inputFormat: 'json' | 'csv' | 'unknown' = 'unknown';
202
+ const inputData = req.body;
203
+
204
+ if (autoDetect) {
205
+ if (contentType.includes('application/json') ||
206
+ (req.body !== null && typeof req.body === 'object' && !Array.isArray(req.body))) {
207
+ inputFormat = 'json';
208
+ } else if (contentType.includes('text/csv') ||
209
+ contentType.includes('text/plain') ||
210
+ (typeof req.body === 'string' && req.body.includes(','))) {
211
+ inputFormat = 'csv';
212
+ }
213
+ } else {
214
+ // Ручное определение на основе content-type
215
+ if (contentType.includes('application/json')) {
216
+ inputFormat = 'json';
217
+ } else if (contentType.includes('text/csv')) {
218
+ inputFormat = 'csv';
219
+ }
220
+ }
221
+
222
+ // Если формат не определен, пропускаем
223
+ if (inputFormat === 'unknown') {
224
+ return next();
225
+ }
226
+
227
+ // Установка таймаута
228
+ let timeoutId: NodeJS.Timeout;
229
+ const timeoutPromise = new Promise((_, reject) => {
230
+ timeoutId = setTimeout(() => {
231
+ reject(new Error(`Request processing timeout (${timeout}ms)`));
232
+ }, timeout);
233
+ });
234
+
235
+ try {
236
+ // Обернем основную логику в Promise.race с таймаутом
237
+ const processingPromise = (async () => {
238
+ // Определяем желаемый формат вывода на основе Accept header
239
+ let outputFormat: 'json' | 'csv' = 'json';
240
+ if (acceptHeader.includes('text/csv')) {
241
+ outputFormat = 'csv';
242
+ } else if (req.query.format === 'csv') {
243
+ outputFormat = 'csv';
244
+ } else if ((req.body as any)?.format === 'csv') {
245
+ outputFormat = 'csv';
246
+ }
247
+
248
+ // Опции конвертации
249
+ const mergedConversionOptions: Record<string, any> = {
250
+ delimiter,
251
+ preventCsvInjection,
252
+ rfc4180Compliant,
253
+ useFastPath: enableFastPath,
254
+ maxFieldSize,
255
+ useWorkers,
256
+ workerCount,
257
+ ...req.query,
258
+ ...conversionOptions
259
+ };
260
+
261
+ // Удаляем параметры, которые не относятся к конвертации
262
+ delete mergedConversionOptions.maxSize;
263
+ delete mergedConversionOptions.maxFileSize;
264
+ delete mergedConversionOptions.maxFieldSize;
265
+ delete mergedConversionOptions.timeout;
266
+ delete mergedConversionOptions.autoDetect;
267
+ delete mergedConversionOptions.enableFastPath;
268
+ delete mergedConversionOptions.useAsync;
269
+ delete mergedConversionOptions.useWorkers;
270
+ delete mergedConversionOptions.workerCount;
271
+
272
+ let result: any;
273
+ const stats: ConvertedData['stats'] = {
274
+ inputSize: 0,
275
+ outputSize: 0,
276
+ processingTime: 0,
277
+ conversion: `${inputFormat}→${outputFormat}`
278
+ };
279
+
280
+ const startTime = Date.now();
281
+
282
+ // Выполняем конвертацию
283
+ if (inputFormat === 'json' && outputFormat === 'csv') {
284
+ const jsonData = typeof inputData === 'string' ? JSON.parse(inputData) : inputData;
285
+ stats.inputSize = Buffer.byteLength(JSON.stringify(jsonData));
286
+
287
+ if (useAsync) {
288
+ result = await jsonToCsvAsync(jsonData, mergedConversionOptions);
289
+ } else {
290
+ result = jsonToCsv(jsonData, mergedConversionOptions);
291
+ }
292
+ stats.outputSize = Buffer.byteLength(result);
293
+
294
+ } else if (inputFormat === 'csv' && outputFormat === 'json') {
295
+ const csvData = typeof inputData === 'string' ? inputData : String(inputData);
296
+ stats.inputSize = Buffer.byteLength(csvData);
297
+
298
+ if (useAsync) {
299
+ result = await csvToJsonAsync(csvData, mergedConversionOptions);
300
+ } else {
301
+ result = csvToJson(csvData, mergedConversionOptions);
302
+ }
303
+ stats.outputSize = Buffer.byteLength(JSON.stringify(result));
304
+
305
+ } else {
306
+ // Нет необходимости в конвертации
307
+ result = inputData;
308
+ stats.conversion = 'none';
309
+ }
310
+
311
+ stats.processingTime = Date.now() - startTime;
312
+
313
+ // Сохраняем результат в request object
314
+ req.converted = {
315
+ data: result,
316
+ format: outputFormat,
317
+ inputFormat,
318
+ outputFormat,
319
+ stats,
320
+ options: mergedConversionOptions
321
+ };
322
+
323
+ // Устанавливаем соответствующий Content-Type для ответа
324
+ if (outputFormat === 'csv') {
325
+ res.set('Content-Type', 'text/csv; charset=utf-8');
326
+ } else {
327
+ res.set('Content-Type', 'application/json; charset=utf-8');
328
+ }
329
+
330
+ next();
331
+ })();
332
+
333
+ // Ждем либо обработку, либо таймаут
334
+ await Promise.race([processingPromise, timeoutPromise]);
335
+ } catch (error) {
336
+ // Обработка ошибок конвертации
337
+ const err = error as Error & { code?: string };
338
+ if (process.env.NODE_ENV !== 'test') {
339
+ console.log('[jtcsv-middleware] Conversion error:', err.message, err.stack?.split('\n')[0]);
340
+ }
341
+
342
+ // Определяем статус код на основе типа ошибки
343
+ let statusCode = 400;
344
+ if (err.message.includes('timeout') || err.message.includes('Timeout')) {
345
+ statusCode = 408; // Request Timeout
346
+ } else if (err.message.includes('File size exceeds limit')) {
347
+ statusCode = 413; // Payload Too Large
348
+ } else if (err instanceof SecurityError) {
349
+ statusCode = 403; // Forbidden
350
+ } else if (err instanceof ValidationError) {
351
+ statusCode = 422; // Unprocessable Entity
352
+ } else if (err instanceof FileSystemError) {
353
+ statusCode = 500; // Internal Server Error
354
+ } else if (err instanceof JtcsvError) {
355
+ statusCode = 400; // Bad Request
356
+ }
357
+
358
+ const errorResponse: any = {
359
+ success: false,
360
+ error: err.message,
361
+ code: err.code || 'CONVERSION_ERROR',
362
+ timestamp: new Date().toISOString()
363
+ };
364
+
365
+ // Добавляем дополнительную информацию для отладки
366
+ if (process.env.NODE_ENV === 'development') {
367
+ errorResponse.stack = err.stack;
368
+ errorResponse.details = {
369
+ contentType: req.get('content-type'),
370
+ contentLength: req.get('content-length'),
371
+ method: req.method,
372
+ url: req.url
373
+ };
374
+ }
375
+
376
+ res.status(statusCode).json(errorResponse);
377
+ } finally {
378
+ // Очищаем таймаут
379
+ if (timeoutId) {
380
+ clearTimeout(timeoutId);
381
+ }
382
+ }
383
+ };
384
+ }
385
+
386
+ /**
387
+ * Express route для конвертации CSV в JSON
388
+ *
389
+ * @param options - Опции конвертации
390
+ * @returns Express route handler
391
+ *
392
+ * @example
393
+ * app.post('/api/csv-to-json', jtcsvCsvToJsonRoute());
394
+ */
395
+ export function jtcsvCsvToJsonRoute(options: Record<string, any> = {}): (req: Request, res: Response) => Promise<void> {
396
+ return async (req: Request, res: Response): Promise<void> => {
397
+ try {
398
+ const csvData = req.body;
399
+
400
+ if (!csvData || (typeof csvData !== 'string' && !Buffer.isBuffer(csvData))) {
401
+ res.status(400).json({
402
+ success: false,
403
+ error: 'CSV data is required'
404
+ });
405
+ return;
406
+ }
407
+
408
+ const csvString = Buffer.isBuffer(csvData) ? csvData.toString() : csvData;
409
+ const result = await csvToJsonAsync(csvString, options);
410
+
411
+ res.json({
412
+ success: true,
413
+ data: result,
414
+ stats: {
415
+ rows: result.length,
416
+ processingTime: Date.now() - (req.startTime || Date.now())
417
+ }
418
+ });
419
+ } catch (error) {
420
+ const err = error as Error;
421
+ res.status(400).json({
422
+ success: false,
423
+ error: err.message
424
+ });
425
+ }
426
+ };
427
+ }
428
+
429
+ /**
430
+ * Express route для конвертации JSON в CSV
431
+ *
432
+ * @param options - Опции конвертации
433
+ * @returns Express route handler
434
+ *
435
+ * @example
436
+ * app.post('/api/json-to-csv', jtcsvJsonToCsvRoute());
437
+ */
438
+ export function jtcsvJsonToCsvRoute(options: Record<string, any> = {}): (req: Request, res: Response) => Promise<void> {
439
+ return async (req: Request, res: Response): Promise<void> => {
440
+ try {
441
+ const jsonData = req.body;
442
+
443
+ if (!jsonData || (typeof jsonData !== 'object' && typeof jsonData !== 'string')) {
444
+ res.status(400).json({
445
+ success: false,
446
+ error: 'JSON data is required'
447
+ });
448
+ return;
449
+ }
450
+
451
+ const data = typeof jsonData === 'string' ? JSON.parse(jsonData) : jsonData;
452
+ const result = await jsonToCsvAsync(data, options);
453
+
454
+ res.set('Content-Type', 'text/csv; charset=utf-8');
455
+ res.set('Content-Disposition', 'attachment; filename="data.csv"');
456
+
457
+ res.send(result);
458
+ } catch (error) {
459
+ const err = error as Error;
460
+ res.status(400).json({
461
+ success: false,
462
+ error: err.message
463
+ });
464
+ }
465
+ };
466
+ }
467
+
468
+ /**
469
+ * Express route для загрузки CSV файла
470
+ *
471
+ * @param options - Опции конвертации
472
+ * @returns Express route handler
473
+ *
474
+ * @example
475
+ * const multer = require('multer');
476
+ * const upload = multer({ dest: 'uploads/' });
477
+ * app.post('/api/upload-csv', upload.single('file'), jtcsvUploadCsvRoute());
478
+ */
479
+ export function jtcsvUploadCsvRoute(options: Record<string, any> = {}): (req: Request, res: Response) => Promise<void> {
480
+ return async (req: Request, res: Response): Promise<void> => {
481
+ try {
482
+ if (!(req as any).file) {
483
+ res.status(400).json({
484
+ success: false,
485
+ error: 'CSV file is required'
486
+ });
487
+ return;
488
+ }
489
+
490
+ const fs = require('fs').promises;
491
+ const csvData = await fs.readFile((req as any).file.path, 'utf8');
492
+
493
+ const result = await csvToJsonAsync(csvData, options);
494
+
495
+ // Очищаем временный файл
496
+ await fs.unlink((req as any).file.path);
497
+
498
+ res.json({
499
+ success: true,
500
+ data: result,
501
+ stats: {
502
+ rows: result.length,
503
+ fileSize: (req as any).file.size,
504
+ processingTime: Date.now() - (req.startTime || Date.now())
505
+ }
506
+ });
507
+ } catch (error) {
508
+ const err = error as Error;
509
+ res.status(400).json({
510
+ success: false,
511
+ error: err.message
512
+ });
513
+ }
514
+ };
515
+ }
516
+
517
+ /**
518
+ * Health check endpoint для JTCSV
519
+ *
520
+ * @returns Express route handler
521
+ *
522
+ * @example
523
+ * app.get('/api/health', jtcsvHealthCheck());
524
+ */
525
+ export function jtcsvHealthCheck(): (req: Request, res: Response) => void {
526
+ return (req: Request, res: Response): void => {
527
+ res.json({
528
+ service: 'jtcsv-express-middleware',
529
+ status: 'healthy',
530
+ version: '2.0.0',
531
+ timestamp: new Date().toISOString(),
532
+ features: {
533
+ csvToJson: true,
534
+ jsonToCsv: true,
535
+ fastPathEngine: true,
536
+ csvInjectionProtection: true,
537
+ streaming: true,
538
+ asyncProcessing: true,
539
+ workerPool: true
540
+ }
541
+ });
542
+ };
543
+ }
544
+
545
+ // Экспорт всех функций
546
+ export default {
547
+ middleware: jtcsvExpressMiddleware,
548
+ csvToJsonRoute: jtcsvCsvToJsonRoute,
549
+ jsonToCsvRoute: jtcsvJsonToCsvRoute,
550
+ uploadCsvRoute: jtcsvUploadCsvRoute,
551
+ healthCheck: jtcsvHealthCheck,
552
+
553
+ // Aliases для удобства
554
+ jtcsvMiddleware: jtcsvExpressMiddleware,
555
+ createMiddleware: jtcsvExpressMiddleware
556
+ };
557
+
@@ -234,7 +234,7 @@ async function jtcsvFastifyPlugin(fastify, options = {}) {
234
234
 
235
235
  try {
236
236
  let inputFormat = 'unknown';
237
- let outputFormat = format || (acceptHeader.includes('text/csv') ? 'csv' : 'json');
237
+ const outputFormat = format || (acceptHeader.includes('text/csv') ? 'csv' : 'json');
238
238
 
239
239
  // Определяем формат входных данных
240
240
  if (contentType.includes('application/json') || Array.isArray(request.body)) {
@@ -255,7 +255,7 @@ async function jtcsvFastifyPlugin(fastify, options = {}) {
255
255
  }
256
256
 
257
257
  let result;
258
- let stats = {
258
+ const stats = {
259
259
  inputSize: 0,
260
260
  outputSize: 0,
261
261
  processingTime: 0,
@@ -402,5 +402,3 @@ module.exports = fp(jtcsvFastifyPlugin, {
402
402
 
403
403
  // Экспортируем также как обычную функцию
404
404
  module.exports.jtcsvFastifyPlugin = jtcsvFastifyPlugin;
405
-
406
-