jtcsv 2.2.8 → 3.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 (246) hide show
  1. package/README.md +204 -115
  2. package/bin/jtcsv.ts +2612 -0
  3. package/browser.d.ts +142 -0
  4. package/dist/benchmark.js +446 -0
  5. package/dist/benchmark.js.map +1 -0
  6. package/dist/bin/jtcsv.js +1940 -0
  7. package/dist/bin/jtcsv.js.map +1 -0
  8. package/dist/csv-to-json.js +1262 -0
  9. package/dist/csv-to-json.js.map +1 -0
  10. package/dist/errors.js +291 -0
  11. package/dist/errors.js.map +1 -0
  12. package/dist/eslint.config.js +147 -0
  13. package/dist/eslint.config.js.map +1 -0
  14. package/dist/index-core.js +95 -0
  15. package/dist/index-core.js.map +1 -0
  16. package/dist/index.js +93 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/json-save.js +229 -0
  19. package/dist/json-save.js.map +1 -0
  20. package/dist/json-to-csv.js +576 -0
  21. package/dist/json-to-csv.js.map +1 -0
  22. package/dist/jtcsv-core.cjs.js +1736 -0
  23. package/dist/jtcsv-core.cjs.js.map +1 -0
  24. package/dist/jtcsv-core.esm.js +1708 -0
  25. package/dist/jtcsv-core.esm.js.map +1 -0
  26. package/dist/jtcsv-core.umd.js +1742 -0
  27. package/dist/jtcsv-core.umd.js.map +1 -0
  28. package/dist/jtcsv-full.cjs.js +2241 -0
  29. package/dist/jtcsv-full.cjs.js.map +1 -0
  30. package/dist/jtcsv-full.esm.js +2209 -0
  31. package/dist/jtcsv-full.esm.js.map +1 -0
  32. package/dist/jtcsv-full.umd.js +2247 -0
  33. package/dist/jtcsv-full.umd.js.map +1 -0
  34. package/dist/jtcsv-workers.esm.js +768 -0
  35. package/dist/jtcsv-workers.esm.js.map +1 -0
  36. package/dist/jtcsv-workers.umd.js +782 -0
  37. package/dist/jtcsv-workers.umd.js.map +1 -0
  38. package/dist/jtcsv.cjs.js +1996 -2048
  39. package/dist/jtcsv.cjs.js.map +1 -1
  40. package/dist/jtcsv.esm.js +1992 -2048
  41. package/dist/jtcsv.esm.js.map +1 -1
  42. package/dist/jtcsv.umd.js +2157 -2209
  43. package/dist/jtcsv.umd.js.map +1 -1
  44. package/dist/plugins/express-middleware/index.js +350 -0
  45. package/dist/plugins/express-middleware/index.js.map +1 -0
  46. package/dist/plugins/fastify-plugin/index.js +315 -0
  47. package/dist/plugins/fastify-plugin/index.js.map +1 -0
  48. package/dist/plugins/hono/index.js +111 -0
  49. package/dist/plugins/hono/index.js.map +1 -0
  50. package/dist/plugins/nestjs/index.js +112 -0
  51. package/dist/plugins/nestjs/index.js.map +1 -0
  52. package/dist/plugins/nuxt/index.js +53 -0
  53. package/dist/plugins/nuxt/index.js.map +1 -0
  54. package/dist/plugins/remix/index.js +133 -0
  55. package/dist/plugins/remix/index.js.map +1 -0
  56. package/dist/plugins/sveltekit/index.js +155 -0
  57. package/dist/plugins/sveltekit/index.js.map +1 -0
  58. package/dist/plugins/trpc/index.js +136 -0
  59. package/dist/plugins/trpc/index.js.map +1 -0
  60. package/dist/run-demo.js +49 -0
  61. package/dist/run-demo.js.map +1 -0
  62. package/dist/src/browser/browser-functions.js +193 -0
  63. package/dist/src/browser/browser-functions.js.map +1 -0
  64. package/dist/src/browser/core.js +123 -0
  65. package/dist/src/browser/core.js.map +1 -0
  66. package/dist/src/browser/csv-to-json-browser.js +353 -0
  67. package/dist/src/browser/csv-to-json-browser.js.map +1 -0
  68. package/dist/src/browser/errors-browser.js +219 -0
  69. package/dist/src/browser/errors-browser.js.map +1 -0
  70. package/dist/src/browser/extensions/plugins.js +106 -0
  71. package/dist/src/browser/extensions/plugins.js.map +1 -0
  72. package/dist/src/browser/extensions/workers.js +66 -0
  73. package/dist/src/browser/extensions/workers.js.map +1 -0
  74. package/dist/src/browser/index.js +140 -0
  75. package/dist/src/browser/index.js.map +1 -0
  76. package/dist/src/browser/json-to-csv-browser.js +225 -0
  77. package/dist/src/browser/json-to-csv-browser.js.map +1 -0
  78. package/dist/src/browser/streams.js +340 -0
  79. package/dist/src/browser/streams.js.map +1 -0
  80. package/dist/src/browser/workers/csv-parser.worker.js +264 -0
  81. package/dist/src/browser/workers/csv-parser.worker.js.map +1 -0
  82. package/dist/src/browser/workers/worker-pool.js +338 -0
  83. package/dist/src/browser/workers/worker-pool.js.map +1 -0
  84. package/dist/src/core/delimiter-cache.js +196 -0
  85. package/dist/src/core/delimiter-cache.js.map +1 -0
  86. package/dist/src/core/node-optimizations.js +279 -0
  87. package/dist/src/core/node-optimizations.js.map +1 -0
  88. package/dist/src/core/plugin-system.js +399 -0
  89. package/dist/src/core/plugin-system.js.map +1 -0
  90. package/dist/src/core/transform-hooks.js +348 -0
  91. package/dist/src/core/transform-hooks.js.map +1 -0
  92. package/dist/src/engines/fast-path-engine-new.js +262 -0
  93. package/dist/src/engines/fast-path-engine-new.js.map +1 -0
  94. package/dist/src/engines/fast-path-engine.js +671 -0
  95. package/dist/src/engines/fast-path-engine.js.map +1 -0
  96. package/dist/src/errors.js +18 -0
  97. package/dist/src/errors.js.map +1 -0
  98. package/dist/src/formats/ndjson-parser.js +332 -0
  99. package/dist/src/formats/ndjson-parser.js.map +1 -0
  100. package/dist/src/formats/tsv-parser.js +230 -0
  101. package/dist/src/formats/tsv-parser.js.map +1 -0
  102. package/dist/src/index-with-plugins.js +259 -0
  103. package/dist/src/index-with-plugins.js.map +1 -0
  104. package/dist/src/types/index.js +3 -0
  105. package/dist/src/types/index.js.map +1 -0
  106. package/dist/src/utils/bom-utils.js +267 -0
  107. package/dist/src/utils/bom-utils.js.map +1 -0
  108. package/dist/src/utils/encoding-support.js +77 -0
  109. package/dist/src/utils/encoding-support.js.map +1 -0
  110. package/dist/src/utils/schema-validator.js +609 -0
  111. package/dist/src/utils/schema-validator.js.map +1 -0
  112. package/dist/src/utils/transform-loader.js +281 -0
  113. package/dist/src/utils/transform-loader.js.map +1 -0
  114. package/dist/src/utils/validators.js +40 -0
  115. package/dist/src/utils/validators.js.map +1 -0
  116. package/dist/src/utils/zod-adapter.js +144 -0
  117. package/dist/src/utils/zod-adapter.js.map +1 -0
  118. package/dist/src/web-server/index.js +648 -0
  119. package/dist/src/web-server/index.js.map +1 -0
  120. package/dist/src/workers/csv-multithreaded.js +211 -0
  121. package/dist/src/workers/csv-multithreaded.js.map +1 -0
  122. package/dist/src/workers/csv-parser.worker.js +179 -0
  123. package/dist/src/workers/csv-parser.worker.js.map +1 -0
  124. package/dist/src/workers/worker-pool.js +228 -0
  125. package/dist/src/workers/worker-pool.js.map +1 -0
  126. package/dist/stream-csv-to-json.js +665 -0
  127. package/dist/stream-csv-to-json.js.map +1 -0
  128. package/dist/stream-json-to-csv.js +389 -0
  129. package/dist/stream-json-to-csv.js.map +1 -0
  130. package/examples/advanced/conditional-transformations.ts +446 -0
  131. package/examples/advanced/csv-parser.worker.ts +89 -0
  132. package/examples/advanced/nested-objects-example.ts +306 -0
  133. package/examples/advanced/performance-optimization.ts +504 -0
  134. package/examples/advanced/run-demo-server.ts +116 -0
  135. package/examples/advanced/web-worker-usage.html +874 -0
  136. package/examples/async-multithreaded-example.ts +335 -0
  137. package/examples/cli-advanced-usage.md +290 -0
  138. package/examples/{cli-batch-processing.js → cli-batch-processing.ts} +38 -38
  139. package/examples/{cli-tool.js → cli-tool.ts} +5 -8
  140. package/examples/{error-handling.js → error-handling.ts} +356 -324
  141. package/examples/{express-api.js → express-api.ts} +161 -164
  142. package/examples/{large-dataset-example.js → large-dataset-example.ts} +201 -182
  143. package/examples/{ndjson-processing.js → ndjson-processing.ts} +456 -434
  144. package/examples/{plugin-excel-exporter.js → plugin-excel-exporter.ts} +6 -7
  145. package/examples/react-integration.tsx +637 -0
  146. package/examples/{schema-validation.js → schema-validation.ts} +2 -2
  147. package/examples/simple-usage.ts +194 -0
  148. package/examples/{streaming-example.js → streaming-example.ts} +12 -12
  149. package/index.d.ts +187 -18
  150. package/package.json +75 -81
  151. package/plugins.d.ts +37 -0
  152. package/schema.d.ts +103 -0
  153. package/src/browser/browser-functions.ts +402 -0
  154. package/src/browser/core.ts +152 -0
  155. package/src/browser/csv-to-json-browser.d.ts +3 -0
  156. package/src/browser/csv-to-json-browser.ts +494 -0
  157. package/src/browser/{errors-browser.js → errors-browser.ts} +305 -197
  158. package/src/browser/extensions/plugins.ts +93 -0
  159. package/src/browser/extensions/workers.ts +39 -0
  160. package/src/browser/globals.d.ts +5 -0
  161. package/src/browser/index.ts +192 -0
  162. package/src/browser/json-to-csv-browser.d.ts +3 -0
  163. package/src/browser/json-to-csv-browser.ts +338 -0
  164. package/src/browser/streams.ts +403 -0
  165. package/src/browser/workers/{csv-parser.worker.js → csv-parser.worker.ts} +3 -3
  166. package/src/browser/workers/{worker-pool.js → worker-pool.ts} +51 -30
  167. package/src/core/delimiter-cache.ts +320 -0
  168. package/src/core/{node-optimizations.js → node-optimizations.ts} +448 -407
  169. package/src/core/plugin-system.ts +588 -0
  170. package/src/core/transform-hooks.ts +566 -0
  171. package/src/engines/{fast-path-engine-new.js → fast-path-engine-new.ts} +11 -2
  172. package/src/engines/{fast-path-engine.js → fast-path-engine.ts} +79 -53
  173. package/src/errors.ts +1 -0
  174. package/src/formats/{ndjson-parser.js → ndjson-parser.ts} +24 -16
  175. package/src/formats/{tsv-parser.js → tsv-parser.ts} +18 -17
  176. package/src/{index-with-plugins.js → index-with-plugins.ts} +381 -357
  177. package/src/types/index.ts +275 -0
  178. package/src/utils/bom-utils.ts +373 -0
  179. package/src/utils/encoding-support.ts +155 -0
  180. package/src/utils/{schema-validator.js → schema-validator.ts} +814 -589
  181. package/src/utils/transform-loader.ts +389 -0
  182. package/src/utils/validators.ts +35 -0
  183. package/src/utils/zod-adapter.ts +280 -0
  184. package/src/web-server/{index.js → index.ts} +19 -19
  185. package/src/workers/csv-multithreaded.ts +310 -0
  186. package/src/workers/csv-parser.worker.ts +227 -0
  187. package/src/workers/worker-pool.ts +409 -0
  188. package/bin/jtcsv.js +0 -2462
  189. package/csv-to-json.js +0 -688
  190. package/errors.js +0 -208
  191. package/examples/simple-usage.js +0 -282
  192. package/index.js +0 -68
  193. package/json-save.js +0 -254
  194. package/json-to-csv.js +0 -526
  195. package/plugins/README.md +0 -91
  196. package/plugins/express-middleware/README.md +0 -64
  197. package/plugins/express-middleware/example.js +0 -136
  198. package/plugins/express-middleware/index.d.ts +0 -114
  199. package/plugins/express-middleware/index.js +0 -360
  200. package/plugins/express-middleware/package.json +0 -52
  201. package/plugins/fastify-plugin/index.js +0 -406
  202. package/plugins/fastify-plugin/package.json +0 -55
  203. package/plugins/hono/README.md +0 -28
  204. package/plugins/hono/index.d.ts +0 -12
  205. package/plugins/hono/index.js +0 -36
  206. package/plugins/hono/package.json +0 -35
  207. package/plugins/nestjs/README.md +0 -35
  208. package/plugins/nestjs/index.d.ts +0 -25
  209. package/plugins/nestjs/index.js +0 -77
  210. package/plugins/nestjs/package.json +0 -37
  211. package/plugins/nextjs-api/README.md +0 -57
  212. package/plugins/nextjs-api/examples/ConverterComponent.jsx +0 -386
  213. package/plugins/nextjs-api/examples/api-convert.js +0 -69
  214. package/plugins/nextjs-api/index.js +0 -387
  215. package/plugins/nextjs-api/package.json +0 -63
  216. package/plugins/nextjs-api/route.js +0 -371
  217. package/plugins/nuxt/README.md +0 -24
  218. package/plugins/nuxt/index.js +0 -21
  219. package/plugins/nuxt/package.json +0 -35
  220. package/plugins/nuxt/runtime/composables/useJtcsv.js +0 -6
  221. package/plugins/nuxt/runtime/plugin.js +0 -6
  222. package/plugins/remix/README.md +0 -26
  223. package/plugins/remix/index.d.ts +0 -16
  224. package/plugins/remix/index.js +0 -62
  225. package/plugins/remix/package.json +0 -35
  226. package/plugins/sveltekit/README.md +0 -28
  227. package/plugins/sveltekit/index.d.ts +0 -17
  228. package/plugins/sveltekit/index.js +0 -54
  229. package/plugins/sveltekit/package.json +0 -33
  230. package/plugins/trpc/README.md +0 -25
  231. package/plugins/trpc/index.d.ts +0 -7
  232. package/plugins/trpc/index.js +0 -32
  233. package/plugins/trpc/package.json +0 -34
  234. package/src/browser/browser-functions.js +0 -219
  235. package/src/browser/csv-to-json-browser.js +0 -700
  236. package/src/browser/index.js +0 -113
  237. package/src/browser/json-to-csv-browser.js +0 -309
  238. package/src/browser/streams.js +0 -393
  239. package/src/core/delimiter-cache.js +0 -186
  240. package/src/core/plugin-system.js +0 -476
  241. package/src/core/transform-hooks.js +0 -350
  242. package/src/errors.js +0 -26
  243. package/src/utils/transform-loader.js +0 -205
  244. package/stream-csv-to-json.js +0 -542
  245. package/stream-json-to-csv.js +0 -464
  246. /package/examples/{web-workers-advanced.js → web-workers-advanced.ts} +0 -0
@@ -5,11 +5,11 @@
5
5
  * with REST API endpoints
6
6
  */
7
7
 
8
- const http = require('http');
9
- const fs = require('fs');
10
- const path = require('path');
11
- const url = require('url');
12
- const jtcsv = require('../../index.js');
8
+ import http from "http";
9
+ import fs from "fs";
10
+ import path from "path";
11
+ import url from "url";
12
+ import * as jtcsv from "../../index";
13
13
 
14
14
  const PORT = process.env.PORT || 3000;
15
15
  const HOST = process.env.HOST || 'localhost';
@@ -17,7 +17,7 @@ const HOST = process.env.HOST || 'localhost';
17
17
  /**
18
18
  * Parse JSON body from request
19
19
  */
20
- function parseBody(req) {
20
+ function parseBody(req): Promise<any> {
21
21
  return new Promise((resolve, reject) => {
22
22
  let body = '';
23
23
  req.on('data', chunk => {
@@ -126,7 +126,7 @@ async function handleValidate(req, res) {
126
126
  const { data, format } = body;
127
127
 
128
128
  let isValid = false;
129
- let errors = [];
129
+ const errors = [];
130
130
 
131
131
  if (format === 'json') {
132
132
  if (Array.isArray(data)) {
@@ -644,28 +644,28 @@ function handleRequest(req, res) {
644
644
  /**
645
645
  * Start server
646
646
  */
647
- function startServer(options = {}) {
647
+ function startServer(options: any = {}) {
648
648
  const port = options.port || PORT;
649
649
  const host = options.host || HOST;
650
650
 
651
651
  const server = http.createServer(handleRequest);
652
652
 
653
653
  server.listen(port, host, () => {
654
- console.log(`\n🌐 JTCSV Web Server started!`);
654
+ console.log('\n🌐 JTCSV Web Server started!');
655
655
  console.log(`\n📍 URL: http://${host}:${port}`);
656
- console.log(`\n📡 API Endpoints:`);
657
- console.log(` POST /api/json-to-csv`);
658
- console.log(` POST /api/csv-to-json`);
659
- console.log(` POST /api/ndjson-to-csv`);
660
- console.log(` POST /api/csv-to-ndjson`);
661
- console.log(` POST /api/validate`);
662
- console.log(`\n✨ Press Ctrl+C to stop\n`);
656
+ console.log('\n📡 API Endpoints:');
657
+ console.log(' POST /api/json-to-csv');
658
+ console.log(' POST /api/csv-to-json');
659
+ console.log(' POST /api/ndjson-to-csv');
660
+ console.log(' POST /api/csv-to-ndjson');
661
+ console.log(' POST /api/validate');
662
+ console.log('\n✨ Press Ctrl+C to stop\n');
663
663
  });
664
664
 
665
665
  server.on('error', (error) => {
666
- if (error.code === 'EADDRINUSE') {
666
+ if ((error as any).code === 'EADDRINUSE') {
667
667
  console.error(`\n❌ Error: Port ${port} is already in use`);
668
- console.error(` Try a different port: jtcsv web --port=3001\n`);
668
+ console.error(' Try a different port: jtcsv web --port=3001\n');
669
669
  } else {
670
670
  console.error(`\n❌ Server error: ${error.message}\n`);
671
671
  }
@@ -675,7 +675,7 @@ function startServer(options = {}) {
675
675
  return server;
676
676
  }
677
677
 
678
- module.exports = { startServer };
678
+ export default { startServer };
679
679
 
680
680
  // Run as standalone
681
681
  if (require.main === module) {
@@ -0,0 +1,310 @@
1
+ /**
2
+ * Многопоточная обработка CSV данных
3
+ *
4
+ * Интеграция Worker Pool с существующими асинхронными функциями
5
+ */
6
+
7
+ import { getWorkerPool, createWorkerTask } from './worker-pool';
8
+ import { CsvToJsonOptions, AsyncCsvToJsonOptions, AnyArray, AnyObject } from '../types';
9
+ import { chunkData, createChunkTasks, mergeChunkResults } from './csv-parser.worker';
10
+
11
+ /**
12
+ * Многопоточная версия csvToJson
13
+ *
14
+ * @param csv - CSV строка для парсинга
15
+ * @param options - Опции с поддержкой многопоточности
16
+ * @returns Promise с результатом парсинга
17
+ */
18
+ export async function csvToJsonMultithreaded(
19
+ csv: string,
20
+ options: AsyncCsvToJsonOptions = {}
21
+ ): Promise<AnyArray> {
22
+ const {
23
+ useWorkers = true,
24
+ workerCount,
25
+ chunkSize = 1000,
26
+ onProgress,
27
+ ...csvOptions
28
+ } = options;
29
+
30
+ // Если многопоточность отключена или данные маленькие, используем обычную версию
31
+ if (!useWorkers || csv.length < 10000) {
32
+ const { csvToJson } = await import('../../csv-to-json');
33
+ return csvToJson(csv, csvOptions);
34
+ }
35
+
36
+ // Разделяем CSV на строки
37
+ const lines = csv.split('\n').filter(line => line.trim() !== '');
38
+
39
+ if (lines.length === 0) {
40
+ return [];
41
+ }
42
+
43
+ // Определяем есть ли заголовки
44
+ const hasHeaders = csvOptions.hasHeaders !== false;
45
+
46
+ // Разделяем данные на чанки
47
+ let dataChunks: string[][];
48
+ let headers: string[] = [];
49
+
50
+ if (hasHeaders) {
51
+ headers = lines[0].split(csvOptions.delimiter || ';');
52
+ const dataLines = lines.slice(1);
53
+ dataChunks = chunkData(dataLines, chunkSize);
54
+ } else {
55
+ dataChunks = chunkData(lines, chunkSize);
56
+ }
57
+
58
+ // Создаем задачи для воркеров
59
+ const tasks = dataChunks.map((chunk, index) => {
60
+ // Восстанавливаем CSV чанк с заголовками если нужно
61
+ let chunkCsv = chunk.join('\n');
62
+ if (hasHeaders && headers.length > 0) {
63
+ chunkCsv = headers.join(csvOptions.delimiter || ';') + '\n' + chunkCsv;
64
+ }
65
+
66
+ return createWorkerTask('csv_parse', chunkCsv, {
67
+ ...csvOptions,
68
+ chunkIndex: index,
69
+ totalChunks: dataChunks.length,
70
+ hasHeaders: index === 0 ? hasHeaders : false // Только первый чанк получает заголовки
71
+ });
72
+ });
73
+
74
+ // Получаем пул воркеров
75
+ const workerPool = getWorkerPool(workerCount);
76
+
77
+ // Отправляем прогресс если есть callback
78
+ if (onProgress) {
79
+ workerPool.on('taskCompleted', ({ task }) => {
80
+ const chunkIndex = task.options?.chunkIndex || 0;
81
+ const totalChunks = task.options?.totalChunks || 1;
82
+
83
+ onProgress({
84
+ processed: chunkIndex + 1,
85
+ total: totalChunks,
86
+ percentage: Math.round(((chunkIndex + 1) / totalChunks) * 100)
87
+ });
88
+ });
89
+ }
90
+
91
+ // Выполняем задачи параллельно
92
+ const results = await workerPool.executeTasks(tasks);
93
+
94
+ // Объединяем результаты
95
+ const mergedResults = mergeChunkResults(results);
96
+
97
+ return mergedResults;
98
+ }
99
+
100
+ /**
101
+ * Многопоточная версия jsonToCsv
102
+ *
103
+ * @param data - JSON данные для конвертации
104
+ * @param options - Опции с поддержкой многопоточности
105
+ * @returns Promise с CSV строкой
106
+ */
107
+ export async function jsonToCsvMultithreaded(
108
+ data: AnyArray | AnyObject,
109
+ options: any = {} // TODO: Добавить тип AsyncJsonToCsvOptions
110
+ ): Promise<string> {
111
+ const {
112
+ useWorkers = true,
113
+ workerCount,
114
+ chunkSize = 1000,
115
+ onProgress,
116
+ ...jsonOptions
117
+ } = options;
118
+
119
+ // Подготавливаем данные
120
+ const dataArray = Array.isArray(data) ? data : [data];
121
+
122
+ // Если многопоточность отключена или данные маленькие, используем обычную версию
123
+ if (!useWorkers || dataArray.length < 1000) {
124
+ const { jsonToCsv } = await import('../../json-to-csv');
125
+ return jsonToCsv(dataArray, jsonOptions);
126
+ }
127
+
128
+ // Разделяем данные на чанки
129
+ const dataChunks = chunkData(dataArray, chunkSize);
130
+
131
+ // Создаем задачи для воркеров
132
+ const tasks = dataChunks.map((chunk, index) => {
133
+ return createWorkerTask('json_to_csv', chunk, {
134
+ ...jsonOptions,
135
+ chunkIndex: index,
136
+ totalChunks: dataChunks.length,
137
+ includeHeaders: index === 0 // Только первый чанк включает заголовки
138
+ });
139
+ });
140
+
141
+ // Получаем пул воркеров
142
+ const workerPool = getWorkerPool(workerCount);
143
+
144
+ // Отправляем прогресс если есть callback
145
+ if (onProgress) {
146
+ workerPool.on('taskCompleted', ({ task }) => {
147
+ const chunkIndex = task.options?.chunkIndex || 0;
148
+ const totalChunks = task.options?.totalChunks || 1;
149
+
150
+ onProgress({
151
+ processed: chunkIndex + 1,
152
+ total: totalChunks,
153
+ percentage: Math.round(((chunkIndex + 1) / totalChunks) * 100)
154
+ });
155
+ });
156
+ }
157
+
158
+ // Выполняем задачи параллельно
159
+ const results = await workerPool.executeTasks(tasks);
160
+
161
+ // Объединяем CSV чанки
162
+ let finalCsv = '';
163
+
164
+ for (let i = 0; i < results.length; i++) {
165
+ const chunkCsv = results[i];
166
+
167
+ if (i === 0) {
168
+ // Первый чанк включает заголовки
169
+ finalCsv = chunkCsv;
170
+ } else {
171
+ // Последующие чанки - только данные (убираем первую строку если это заголовки)
172
+ const lines = chunkCsv.split('\n');
173
+ if (lines.length > 1 && jsonOptions.includeHeaders !== false) {
174
+ finalCsv += '\n' + lines.slice(1).join('\n');
175
+ } else {
176
+ finalCsv += '\n' + chunkCsv;
177
+ }
178
+ }
179
+ }
180
+
181
+ return finalCsv;
182
+ }
183
+
184
+ /**
185
+ * Бенчмарк многопоточной обработки
186
+ *
187
+ * @param data - Данные для тестирования
188
+ * @param iterations - Количество итераций
189
+ * @returns Результаты бенчмарка
190
+ */
191
+ export async function benchmarkMultithreaded(
192
+ data: AnyArray | string,
193
+ iterations: number = 10
194
+ ): Promise<{
195
+ singleThread: number;
196
+ multiThread: number;
197
+ speedup: number;
198
+ efficiency: number;
199
+ }> {
200
+ const workerPool = getWorkerPool();
201
+ const cpuCount = require('os').cpus().length;
202
+
203
+ // Тестируем однопоточную обработку
204
+ const singleThreadStart = Date.now();
205
+
206
+ if (typeof data === 'string') {
207
+ // CSV парсинг
208
+ const { csvToJson } = await import('../../csv-to-json');
209
+ for (let i = 0; i < iterations; i++) {
210
+ await csvToJson(data);
211
+ }
212
+ } else {
213
+ // JSON to CSV
214
+ const { jsonToCsv } = await import('../../json-to-csv');
215
+ for (let i = 0; i < iterations; i++) {
216
+ await jsonToCsv(data);
217
+ }
218
+ }
219
+
220
+ const singleThreadTime = Date.now() - singleThreadStart;
221
+
222
+ // Тестируем многопоточную обработку
223
+ const multiThreadStart = Date.now();
224
+
225
+ if (typeof data === 'string') {
226
+ for (let i = 0; i < iterations; i++) {
227
+ await csvToJsonMultithreaded(data, {
228
+ useWorkers: true,
229
+ workerCount: cpuCount - 1
230
+ });
231
+ }
232
+ } else {
233
+ for (let i = 0; i < iterations; i++) {
234
+ await jsonToCsvMultithreaded(data, {
235
+ useWorkers: true,
236
+ workerCount: cpuCount - 1
237
+ });
238
+ }
239
+ }
240
+
241
+ const multiThreadTime = Date.now() - multiThreadStart;
242
+
243
+ // Вычисляем ускорение и эффективность
244
+ const speedup = singleThreadTime / multiThreadTime;
245
+ const efficiency = (speedup / (cpuCount - 1)) * 100;
246
+
247
+ return {
248
+ singleThread: singleThreadTime,
249
+ multiThread: multiThreadTime,
250
+ speedup,
251
+ efficiency
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Оптимизирует размер чанка на основе размера данных
257
+ *
258
+ * @param dataSize - Размер данных (количество строк или байт)
259
+ * @param workerCount - Количество воркеров
260
+ * @returns Оптимальный размер чанка
261
+ */
262
+ export function optimizeChunkSize(
263
+ dataSize: number,
264
+ workerCount: number = Math.max(1, require('os').cpus().length - 1)
265
+ ): number {
266
+ // Базовый размер чанка
267
+ let chunkSize = 1000;
268
+
269
+ if (dataSize > 1000000) {
270
+ // Очень большие данные - увеличиваем размер чанка
271
+ chunkSize = 10000;
272
+ } else if (dataSize > 100000) {
273
+ // Большие данные
274
+ chunkSize = 5000;
275
+ } else if (dataSize > 10000) {
276
+ // Средние данные
277
+ chunkSize = 2000;
278
+ } else if (dataSize < 1000) {
279
+ // Маленькие данные - уменьшаем размер чанка
280
+ chunkSize = Math.max(100, Math.ceil(dataSize / workerCount));
281
+ }
282
+
283
+ // Учитываем количество воркеров
284
+ chunkSize = Math.max(chunkSize, Math.ceil(dataSize / (workerCount * 10)));
285
+
286
+ return chunkSize;
287
+ }
288
+
289
+ /**
290
+ * Мониторинг использования ресурсов
291
+ *
292
+ * @returns Статистика использования ресурсов
293
+ */
294
+ export function getResourceUsage(): {
295
+ cpuUsage: NodeJS.CpuUsage;
296
+ memoryUsage: NodeJS.MemoryUsage;
297
+ workerStats: any;
298
+ } {
299
+ const cpuUsage = process.cpuUsage();
300
+ const memoryUsage = process.memoryUsage();
301
+
302
+ const workerPool = getWorkerPool();
303
+ const workerStats = workerPool.getStats();
304
+
305
+ return {
306
+ cpuUsage,
307
+ memoryUsage,
308
+ workerStats
309
+ };
310
+ }
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Worker для парсинга CSV данных
3
+ *
4
+ * Выполняется в отдельном потоке для многопоточной обработки
5
+ */
6
+
7
+ import { parentPort, workerData, isMainThread } from 'worker_threads';
8
+ import { WorkerTask, WorkerResult } from '../types';
9
+
10
+ // Импортируем функции парсинга из основных модулей
11
+ // Используем динамический импорт чтобы избежать циклических зависимостей
12
+ let csvToJson: any = null;
13
+ let jsonToCsv: any = null;
14
+
15
+ /**
16
+ * Инициализирует функции парсинга
17
+ */
18
+ async function initializeParserFunctions(): Promise<void> {
19
+ if (!csvToJson) {
20
+ // Динамический импорт чтобы избежать проблем с циклическими зависимостями
21
+ const csvModule = await import('../../csv-to-json');
22
+ csvToJson = csvModule.csvToJson;
23
+ }
24
+
25
+ if (!jsonToCsv) {
26
+ const jsonModule = await import('../../json-to-csv');
27
+ jsonToCsv = jsonModule.jsonToCsv;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Обрабатывает задачу парсинга CSV
33
+ */
34
+ async function processCsvParsing(task: WorkerTask): Promise<any> {
35
+ await initializeParserFunctions();
36
+
37
+ const { data, options } = task;
38
+
39
+ if (typeof data !== 'string') {
40
+ throw new Error('CSV data must be a string');
41
+ }
42
+
43
+ return csvToJson(data, options);
44
+ }
45
+
46
+ /**
47
+ * Обрабатывает задачу конвертации JSON в CSV
48
+ */
49
+ async function processJsonToCsv(task: WorkerTask): Promise<any> {
50
+ await initializeParserFunctions();
51
+
52
+ const { data, options } = task;
53
+
54
+ if (!Array.isArray(data) && (typeof data !== 'object' || data === null)) {
55
+ throw new Error('JSON data must be an array or object');
56
+ }
57
+
58
+ return jsonToCsv(data, options);
59
+ }
60
+
61
+ /**
62
+ * Обрабатывает задачу обработки чанка данных
63
+ */
64
+ async function processDataChunk(task: WorkerTask): Promise<any> {
65
+ const { type, data, options } = task;
66
+
67
+ switch (type) {
68
+ case 'csv_parse':
69
+ return processCsvParsing(task);
70
+
71
+ case 'json_to_csv':
72
+ return processJsonToCsv(task);
73
+
74
+ case 'transform_data':
75
+ // Простая трансформация данных
76
+ if (Array.isArray(data)) {
77
+ return data.map((item, index) => ({
78
+ ...item,
79
+ _workerId: workerData?.workerId || 0,
80
+ _chunkIndex: index
81
+ }));
82
+ }
83
+ return data;
84
+
85
+ case 'validate_data':
86
+ // Валидация данных
87
+ if (Array.isArray(data)) {
88
+ const invalidItems = data.filter(item =>
89
+ item === null || item === undefined ||
90
+ (typeof item === 'object' && Object.keys(item).length === 0)
91
+ );
92
+
93
+ return {
94
+ valid: invalidItems.length === 0,
95
+ totalItems: data.length,
96
+ invalidItems: invalidItems.length,
97
+ invalidIndexes: data
98
+ .map((item, index) => ({ item, index }))
99
+ .filter(({ item }) =>
100
+ item === null || item === undefined ||
101
+ (typeof item === 'object' && Object.keys(item).length === 0)
102
+ )
103
+ .map(({ index }) => index)
104
+ };
105
+ }
106
+ return { valid: true, totalItems: 1 };
107
+
108
+ default:
109
+ throw new Error(`Unknown task type: ${type}`);
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Основная функция воркера
115
+ */
116
+ async function main() {
117
+ if (isMainThread) {
118
+ console.error('Worker script should not be run in main thread');
119
+ process.exit(1);
120
+ }
121
+
122
+ // Регистрируем обработчик сообщений
123
+ parentPort?.on('message', async (task: WorkerTask) => {
124
+ const startTime = Date.now();
125
+
126
+ try {
127
+ const result = await processDataChunk(task);
128
+ const duration = Date.now() - startTime;
129
+
130
+ const workerResult: WorkerResult = {
131
+ id: task.id,
132
+ result,
133
+ duration
134
+ };
135
+
136
+ parentPort?.postMessage(workerResult);
137
+ } catch (error: any) {
138
+ const duration = Date.now() - startTime;
139
+
140
+ const workerResult: WorkerResult = {
141
+ id: task.id,
142
+ result: null,
143
+ error: error instanceof Error ? error : new Error(String(error)),
144
+ duration
145
+ };
146
+
147
+ parentPort?.postMessage(workerResult);
148
+ }
149
+ });
150
+
151
+ // Отправляем сообщение о готовности
152
+ parentPort?.postMessage({
153
+ type: 'worker_ready',
154
+ workerId: workerData?.workerId || 0,
155
+ pid: process.pid
156
+ });
157
+
158
+ // Обрабатываем сигналы завершения
159
+ process.on('SIGTERM', () => {
160
+ parentPort?.postMessage({
161
+ type: 'worker_shutdown',
162
+ workerId: workerData?.workerId || 0
163
+ });
164
+ process.exit(0);
165
+ });
166
+
167
+ process.on('SIGINT', () => {
168
+ parentPort?.postMessage({
169
+ type: 'worker_shutdown',
170
+ workerId: workerData?.workerId || 0
171
+ });
172
+ process.exit(0);
173
+ });
174
+ }
175
+
176
+ // Запускаем воркер
177
+ main().catch(error => {
178
+ console.error('Worker initialization failed:', error);
179
+ process.exit(1);
180
+ });
181
+
182
+ /**
183
+ * Утилитарные функции для воркера
184
+ */
185
+
186
+ /**
187
+ * Разделяет данные на чанки для параллельной обработки
188
+ */
189
+ export function chunkData<T>(data: T[], chunkSize: number): T[][] {
190
+ const chunks: T[][] = [];
191
+
192
+ for (let i = 0; i < data.length; i += chunkSize) {
193
+ chunks.push(data.slice(i, i + chunkSize));
194
+ }
195
+
196
+ return chunks;
197
+ }
198
+
199
+ /**
200
+ * Объединяет результаты обработки чанков
201
+ */
202
+ export function mergeChunkResults<T>(chunkResults: T[][]): T[] {
203
+ return chunkResults.flat();
204
+ }
205
+
206
+ /**
207
+ * Создает задачи для обработки чанков
208
+ */
209
+ export function createChunkTasks<T, R>(
210
+ data: T[],
211
+ chunkSize: number,
212
+ taskType: string,
213
+ options?: Record<string, any>
214
+ ): WorkerTask<T[], R>[] {
215
+ const chunks = chunkData(data, chunkSize);
216
+
217
+ return chunks.map((chunk, index) => ({
218
+ id: `chunk_${index}_${Date.now()}`,
219
+ type: taskType,
220
+ data: chunk,
221
+ options: {
222
+ ...options,
223
+ chunkIndex: index,
224
+ totalChunks: chunks.length
225
+ }
226
+ }));
227
+ }