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,548 @@
1
+ // Worker Pool для параллельной обработки CSV
2
+ // Использует Comlink для простой коммуникации с Web Workers
3
+
4
+ import { ValidationError, ConfigurationError } from '../errors-browser';
5
+
6
+ // Проверка поддержки Web Workers
7
+ const WORKERS_SUPPORTED = typeof Worker !== 'undefined';
8
+
9
+ function isTransferableBuffer(value) {
10
+ if (!(value instanceof ArrayBuffer)) {
11
+ return false;
12
+ }
13
+ if (typeof SharedArrayBuffer !== 'undefined' && value instanceof SharedArrayBuffer) {
14
+ return false;
15
+ }
16
+ return true;
17
+ }
18
+
19
+ function collectTransferables(args) {
20
+ const transferables = [];
21
+
22
+ const collectFromValue = (value) => {
23
+ if (!value) {
24
+ return;
25
+ }
26
+ if (isTransferableBuffer(value)) {
27
+ transferables.push(value);
28
+ return;
29
+ }
30
+ if (ArrayBuffer.isView(value) && isTransferableBuffer(value.buffer)) {
31
+ transferables.push(value.buffer);
32
+ return;
33
+ }
34
+ if (Array.isArray(value)) {
35
+ value.forEach(collectFromValue);
36
+ }
37
+ };
38
+
39
+ args.forEach(collectFromValue);
40
+ return transferables.length ? transferables : null;
41
+ }
42
+
43
+ type WorkerWithMeta = Worker & {
44
+ id: string;
45
+ status: 'idle' | 'busy' | 'error';
46
+ lastUsed: number;
47
+ taskId: string | null;
48
+ };
49
+
50
+ /**
51
+ * Опции для Worker Pool
52
+ * @typedef {Object} WorkerPoolOptions
53
+ * @property {number} [workerCount=4] - Количество workers в pool
54
+ * @property {number} [maxQueueSize=100] - Максимальный размер очереди задач
55
+ * @property {boolean} [autoScale=true] - Автоматическое масштабирование pool
56
+ * @property {number} [idleTimeout=60000] - Таймаут простоя worker (мс)
57
+ */
58
+
59
+ /**
60
+ * Статистика Worker Pool
61
+ * @typedef {Object} WorkerPoolStats
62
+ * @property {number} totalWorkers - Всего workers
63
+ * @property {number} activeWorkers - Активные workers
64
+ * @property {number} idleWorkers - Простаивающие workers
65
+ * @property {number} queueSize - Размер очереди
66
+ * @property {number} tasksCompleted - Завершенные задачи
67
+ * @property {number} tasksFailed - Неудачные задачи
68
+ */
69
+
70
+ /**
71
+ * Прогресс обработки задачи
72
+ * @typedef {Object} TaskProgress
73
+ * @property {number} processed - Обработано элементов
74
+ * @property {number} total - Всего элементов
75
+ * @property {number} percentage - Процент выполнения
76
+ * @property {number} speed - Скорость обработки (элементов/сек)
77
+ */
78
+
79
+ /**
80
+ * Worker Pool для параллельной обработки CSV
81
+ */
82
+ export class WorkerPool {
83
+ workerScript: string;
84
+ options: any;
85
+ workers: WorkerWithMeta[];
86
+ taskQueue: any[];
87
+ activeTasks: Map<any, any>;
88
+ stats: any;
89
+ /**
90
+ * Создает новый Worker Pool
91
+ * @param {string} workerScript - URL скрипта worker
92
+ * @param {WorkerPoolOptions} [options] - Опции pool
93
+ */
94
+ constructor(workerScript, options = {}) {
95
+ if (!WORKERS_SUPPORTED) {
96
+ throw new ValidationError('Web Workers не поддерживаются в этом браузере');
97
+ }
98
+
99
+ this.workerScript = workerScript;
100
+ this.options = {
101
+ workerCount: 4,
102
+ maxQueueSize: 100,
103
+ autoScale: true,
104
+ idleTimeout: 60000,
105
+ ...options
106
+ };
107
+
108
+ this.workers = [];
109
+ this.taskQueue = [];
110
+ this.activeTasks = new Map();
111
+ this.stats = {
112
+ totalWorkers: 0,
113
+ activeWorkers: 0,
114
+ idleWorkers: 0,
115
+ queueSize: 0,
116
+ tasksCompleted: 0,
117
+ tasksFailed: 0
118
+ };
119
+
120
+ this.initializeWorkers();
121
+ }
122
+
123
+ /**
124
+ * Инициализация workers
125
+ * @private
126
+ */
127
+ initializeWorkers() {
128
+ const { workerCount } = this.options;
129
+
130
+ for (let i = 0; i < workerCount; i++) {
131
+ this.createWorker();
132
+ }
133
+
134
+ this.updateStats();
135
+ }
136
+
137
+ /**
138
+ * Создает нового worker
139
+ * @private
140
+ */
141
+ createWorker() {
142
+ try {
143
+ const worker = new Worker(this.workerScript, { type: 'module' }) as WorkerWithMeta;
144
+
145
+ worker.id = `worker-${this.workers.length}`;
146
+ worker.status = 'idle';
147
+ worker.lastUsed = Date.now();
148
+ worker.taskId = null;
149
+
150
+ // Обработчики событий
151
+ worker.onmessage = (event) => this.handleWorkerMessage(worker, event);
152
+ worker.onerror = (error) => this.handleWorkerError(worker, error);
153
+ worker.onmessageerror = (error) => this.handleWorkerMessageError(worker, error);
154
+
155
+ this.workers.push(worker);
156
+ this.stats.totalWorkers++;
157
+ this.stats.idleWorkers++;
158
+
159
+ return worker;
160
+ } catch (error) {
161
+ throw new ConfigurationError(`Не удалось создать worker: ${error.message}`);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Обработка сообщений от worker
167
+ * @private
168
+ */
169
+ handleWorkerMessage(worker, event) {
170
+ const { data } = event;
171
+
172
+ if (data.type === 'PROGRESS') {
173
+ this.handleProgress(worker, data);
174
+ } else if (data.type === 'RESULT') {
175
+ this.handleResult(worker, data);
176
+ } else if (data.type === 'ERROR') {
177
+ this.handleWorkerTaskError(worker, data);
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Обработка прогресса задачи
183
+ * @private
184
+ */
185
+ handleProgress(worker, progressData) {
186
+ const taskId = worker.taskId;
187
+ if (taskId && this.activeTasks.has(taskId)) {
188
+ const task = this.activeTasks.get(taskId);
189
+ if (task.onProgress) {
190
+ task.onProgress({
191
+ processed: progressData.processed,
192
+ total: progressData.total,
193
+ percentage: (progressData.processed / progressData.total) * 100,
194
+ speed: progressData.speed || 0
195
+ });
196
+ }
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Обработка результата задачи
202
+ * @private
203
+ */
204
+ handleResult(worker, resultData) {
205
+ const taskId = worker.taskId;
206
+ if (taskId && this.activeTasks.has(taskId)) {
207
+ const task = this.activeTasks.get(taskId);
208
+
209
+ // Освобождение worker
210
+ worker.status = 'idle';
211
+ worker.lastUsed = Date.now();
212
+ worker.taskId = null;
213
+ this.stats.activeWorkers--;
214
+ this.stats.idleWorkers++;
215
+
216
+ // Завершение задачи
217
+ task.resolve(resultData.data);
218
+ this.activeTasks.delete(taskId);
219
+ this.stats.tasksCompleted++;
220
+
221
+ // Обработка следующей задачи в очереди
222
+ this.processQueue();
223
+ this.updateStats();
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Обработка ошибки задачи
229
+ * @private
230
+ */
231
+ handleWorkerTaskError(worker, errorData) {
232
+ const taskId = worker.taskId;
233
+ if (taskId && this.activeTasks.has(taskId)) {
234
+ const task = this.activeTasks.get(taskId);
235
+
236
+ // Освобождение worker
237
+ worker.status = 'idle';
238
+ worker.lastUsed = Date.now();
239
+ worker.taskId = null;
240
+ this.stats.activeWorkers--;
241
+ this.stats.idleWorkers++;
242
+
243
+ // Завершение с ошибкой
244
+ const workerError = new Error(errorData.message || 'Ошибка в worker');
245
+ if ((errorData as any).code) {
246
+ (workerError as any).code = (errorData as any).code;
247
+ }
248
+ if ((errorData as any).details) {
249
+ (workerError as any).details = (errorData as any).details;
250
+ }
251
+ task.reject(workerError);
252
+ this.activeTasks.delete(taskId);
253
+ this.stats.tasksFailed++;
254
+
255
+ // Обработка следующей задачи
256
+ this.processQueue();
257
+ this.updateStats();
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Обработка ошибок worker
263
+ * @private
264
+ */
265
+ handleWorkerError(worker, error) {
266
+ console.error(`Worker ${worker.id} error:`, error);
267
+
268
+ // Перезапуск worker
269
+ this.restartWorker(worker);
270
+ }
271
+
272
+ /**
273
+ * Обработка ошибок сообщений
274
+ * @private
275
+ */
276
+ handleWorkerMessageError(worker, error) {
277
+ console.error(`Worker ${worker.id} message error:`, error);
278
+ }
279
+
280
+ /**
281
+ * Перезапуск worker
282
+ * @private
283
+ */
284
+ restartWorker(worker) {
285
+ const index = this.workers.indexOf(worker);
286
+ if (index !== -1) {
287
+ // Завершение старого worker
288
+ worker.terminate();
289
+
290
+ // Удаление из статистики
291
+ if (worker.status === 'active') {
292
+ this.stats.activeWorkers--;
293
+ } else {
294
+ this.stats.idleWorkers--;
295
+ }
296
+ this.stats.totalWorkers--;
297
+
298
+ // Создание нового worker
299
+ const newWorker = this.createWorker();
300
+ this.workers[index] = newWorker;
301
+
302
+ // Перезапуск задачи если была активна
303
+ if (worker.taskId && this.activeTasks.has(worker.taskId)) {
304
+ const task = this.activeTasks.get(worker.taskId);
305
+ this.executeTask(newWorker, task);
306
+ }
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Выполнение задачи на worker
312
+ * @private
313
+ */
314
+ executeTask(worker, task) {
315
+ worker.status = 'active';
316
+ worker.lastUsed = Date.now();
317
+ worker.taskId = task.id;
318
+
319
+ this.stats.idleWorkers--;
320
+ this.stats.activeWorkers++;
321
+
322
+ // Отправка задачи в worker
323
+ const payload = {
324
+ type: 'EXECUTE',
325
+ taskId: task.id,
326
+ method: task.method,
327
+ args: task.args,
328
+ options: task.options
329
+ };
330
+
331
+ if (task.transferList && task.transferList.length) {
332
+ worker.postMessage(payload, task.transferList);
333
+ } else {
334
+ worker.postMessage(payload);
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Обработка очереди задач
340
+ * @private
341
+ */
342
+ processQueue() {
343
+ if (this.taskQueue.length === 0) {
344
+ return;
345
+ }
346
+
347
+ while (this.taskQueue.length > 0) {
348
+ const idleWorker = this.workers.find(w => w.status === 'idle');
349
+ if (!idleWorker) {
350
+ if (this.options.autoScale && this.workers.length < this.options.maxQueueSize) {
351
+ this.createWorker();
352
+ continue;
353
+ }
354
+ break;
355
+ }
356
+
357
+ const task = this.taskQueue.shift();
358
+ this.stats.queueSize--;
359
+ this.executeTask(idleWorker, task);
360
+ }
361
+
362
+ this.updateStats();
363
+ }
364
+
365
+ /**
366
+ * Обновление статистики
367
+ * @private
368
+ */
369
+ updateStats() {
370
+ this.stats.queueSize = this.taskQueue.length;
371
+ }
372
+
373
+ /**
374
+ * Выполнение задачи через pool
375
+ * @param {string} method - Метод для вызова в worker
376
+ * @param {Array} args - Аргументы метода
377
+ * @param {Object} [options] - Опции задачи
378
+ * @param {Function} [onProgress] - Callback прогресса
379
+ * @returns {Promise<unknown>} Результат выполнения
380
+ */
381
+ async exec(method, args = [], options: any = {}, onProgress = null) {
382
+ return new Promise((resolve, reject) => {
383
+ // Проверка размера очереди
384
+ if (this.taskQueue.length >= this.options.maxQueueSize) {
385
+ reject(new Error('Очередь задач переполнена'));
386
+ return;
387
+ }
388
+
389
+ // Создание задачи
390
+ const taskId = `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
391
+ const { transfer, ...taskOptions } = options || {};
392
+ const transferList = transfer || collectTransferables(args);
393
+ const task = {
394
+ id: taskId,
395
+ method,
396
+ args,
397
+ options: taskOptions,
398
+ transferList,
399
+ onProgress,
400
+ resolve,
401
+ reject,
402
+ createdAt: Date.now()
403
+ };
404
+
405
+ // Добавление в очередь
406
+ this.taskQueue.push(task);
407
+ this.stats.queueSize++;
408
+
409
+ // Запуск обработки очереди
410
+ this.processQueue();
411
+ this.updateStats();
412
+ });
413
+ }
414
+
415
+ /**
416
+ * Получение статистики pool
417
+ * @returns {WorkerPoolStats} Статистика
418
+ */
419
+ getStats() {
420
+ return { ...this.stats };
421
+ }
422
+
423
+ /**
424
+ * Очистка простаивающих workers
425
+ */
426
+ cleanupIdleWorkers() {
427
+ const now = Date.now();
428
+ const { idleTimeout } = this.options;
429
+
430
+ for (let i = this.workers.length - 1; i >= 0; i--) {
431
+ const worker = this.workers[i];
432
+ if (worker.status === 'idle' && (now - worker.lastUsed) > idleTimeout) {
433
+ // Сохранение минимального количества workers
434
+ if (this.workers.length > 1) {
435
+ worker.terminate();
436
+ this.workers.splice(i, 1);
437
+ this.stats.totalWorkers--;
438
+ this.stats.idleWorkers--;
439
+ }
440
+ }
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Завершение всех workers
446
+ */
447
+ terminate() {
448
+ this.workers.forEach(worker => {
449
+ worker.terminate();
450
+ });
451
+
452
+ this.workers = [];
453
+ this.taskQueue = [];
454
+ this.activeTasks.clear();
455
+
456
+ // Сброс статистики
457
+ this.stats = {
458
+ totalWorkers: 0,
459
+ activeWorkers: 0,
460
+ idleWorkers: 0,
461
+ queueSize: 0,
462
+ tasksCompleted: 0,
463
+ tasksFailed: 0
464
+ };
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Создает Worker Pool для обработки CSV
470
+ * @param {WorkerPoolOptions} [options] - Опции pool
471
+ * @returns {WorkerPool} Worker Pool
472
+ */
473
+ export function createWorkerPool(options: any = {}): any {
474
+ // Используем встроенный worker скрипт
475
+ const baseUrl =
476
+ typeof document !== 'undefined'
477
+ ? document.baseURI
478
+ : (typeof self !== 'undefined' && (self as any).location
479
+ ? (self as any).location.href
480
+ : '');
481
+ const workerScript = new URL('./csv-parser.worker.js', baseUrl).href;
482
+ return new WorkerPool(workerScript, options);
483
+ }
484
+
485
+ /**
486
+ * Парсит CSV с использованием Web Workers
487
+ * @param {string|File} csvInput - CSV строка или File объект
488
+ * @param {Object} [options] - Опции парсинга
489
+ * @param {Function} [onProgress] - Callback прогресса
490
+ * @returns {Promise<Array<Object>>} JSON данные
491
+ */
492
+ export async function parseCSVWithWorker(csvInput, options: any = {}, onProgress = null) {
493
+ // Создание pool если нужно
494
+ const poolHolder = parseCSVWithWorker as any;
495
+ if (!poolHolder.pool) {
496
+ poolHolder.pool = createWorkerPool();
497
+ }
498
+
499
+ const pool = poolHolder.pool;
500
+
501
+ // Подготовка CSV строки
502
+ // ?????????? CSV ??????
503
+ let csvPayload = csvInput;
504
+ let transfer = null;
505
+
506
+ if (csvInput instanceof File) {
507
+ const buffer = await readFileAsArrayBuffer(csvInput);
508
+ csvPayload = new Uint8Array(buffer);
509
+ transfer = [buffer];
510
+ } else if (csvInput instanceof ArrayBuffer) {
511
+ csvPayload = csvInput;
512
+ transfer = [csvInput];
513
+ } else if (ArrayBuffer.isView(csvInput)) {
514
+ csvPayload = csvInput;
515
+ if (csvInput.buffer instanceof ArrayBuffer) {
516
+ transfer = [csvInput.buffer];
517
+ }
518
+ } else if (typeof csvInput !== 'string') {
519
+ throw new ValidationError('Input must be a CSV string, File, or ArrayBuffer');
520
+ }
521
+
522
+ // ????????? ?????? ????? pool
523
+ const execOptions = transfer ? { transfer } : {};
524
+ return pool.exec('parseCSV', [csvPayload, options], execOptions, onProgress);
525
+ }
526
+
527
+ /**
528
+ * Чтение файла как текст
529
+ * @private
530
+ */
531
+ async function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> {
532
+ return new Promise((resolve, reject) => {
533
+ const reader = new FileReader();
534
+ reader.onload = (event) =>
535
+ resolve((event.target as FileReader).result as ArrayBuffer);
536
+ reader.onerror = (error) => reject(error);
537
+ reader.readAsArrayBuffer(file);
538
+ });
539
+ }
540
+
541
+ // Экспорт для Node.js совместимости
542
+ if (typeof module !== 'undefined' && module.exports) {
543
+ module.exports = {
544
+ WorkerPool,
545
+ createWorkerPool,
546
+ parseCSVWithWorker
547
+ };
548
+ }
@@ -150,28 +150,42 @@ class DelimiterCache {
150
150
  // Используем первую непустую строку для детектирования
151
151
  const firstLine = lines[0];
152
152
 
153
+ // Быстрый подсчёт вхождений кандидатов за один проход
153
154
  const counts = {};
154
- candidates.forEach(delim => {
155
- const escapedDelim = delim.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
156
- const regex = new RegExp(escapedDelim, 'g');
157
- const matches = firstLine.match(regex);
158
- counts[delim] = matches ? matches.length : 0;
159
- });
155
+ const candidateSet = new Set(candidates);
156
+ for (let i = 0; i < firstLine.length; i++) {
157
+ const char = firstLine[i];
158
+ if (candidateSet.has(char)) {
159
+ counts[char] = (counts[char] || 0) + 1;
160
+ }
161
+ }
162
+ // Убедимся, что все кандидаты присутствуют в counts (даже с нулём)
163
+ for (const delim of candidates) {
164
+ if (!(delim in counts)) {
165
+ counts[delim] = 0;
166
+ }
167
+ }
160
168
 
161
169
  // Находим разделитель с максимальным количеством
162
170
  let maxCount = -1;
163
171
  let detectedDelimiter = ';';
172
+ const maxDelimiters = [];
164
173
 
165
174
  for (const [delim, count] of Object.entries(counts)) {
166
175
  if (count > maxCount) {
167
176
  maxCount = count;
168
- detectedDelimiter = delim;
177
+ maxDelimiters.length = 0;
178
+ maxDelimiters.push(delim);
179
+ } else if (count === maxCount) {
180
+ maxDelimiters.push(delim);
169
181
  }
170
182
  }
171
183
 
172
184
  // Если разделитель не найден или есть ничья, возвращаем стандартный
173
- if (maxCount === 0) {
185
+ if (maxCount === 0 || maxDelimiters.length > 1) {
174
186
  detectedDelimiter = ';';
187
+ } else {
188
+ detectedDelimiter = maxDelimiters[0];
175
189
  }
176
190
 
177
191
  // Сохраняем в кэш если он предоставлен