jtcsv 2.2.7 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -1
- package/bin/jtcsv.js +891 -821
- package/bin/jtcsv.ts +2534 -0
- package/csv-to-json.js +168 -145
- package/dist/jtcsv-core.cjs.js +1407 -0
- package/dist/jtcsv-core.cjs.js.map +1 -0
- package/dist/jtcsv-core.esm.js +1379 -0
- package/dist/jtcsv-core.esm.js.map +1 -0
- package/dist/jtcsv-core.umd.js +1413 -0
- package/dist/jtcsv-core.umd.js.map +1 -0
- package/dist/jtcsv-full.cjs.js +1912 -0
- package/dist/jtcsv-full.cjs.js.map +1 -0
- package/dist/jtcsv-full.esm.js +1880 -0
- package/dist/jtcsv-full.esm.js.map +1 -0
- package/dist/jtcsv-full.umd.js +1918 -0
- package/dist/jtcsv-full.umd.js.map +1 -0
- package/dist/jtcsv-workers.esm.js +759 -0
- package/dist/jtcsv-workers.esm.js.map +1 -0
- package/dist/jtcsv-workers.umd.js +773 -0
- package/dist/jtcsv-workers.umd.js.map +1 -0
- package/dist/jtcsv.cjs.js +61 -19
- package/dist/jtcsv.cjs.js.map +1 -1
- package/dist/jtcsv.esm.js +61 -19
- package/dist/jtcsv.esm.js.map +1 -1
- package/dist/jtcsv.umd.js +61 -19
- package/dist/jtcsv.umd.js.map +1 -1
- package/errors.js +188 -2
- package/examples/advanced/conditional-transformations.js +446 -0
- package/examples/advanced/conditional-transformations.ts +446 -0
- package/examples/advanced/csv-parser.worker.js +89 -0
- package/examples/advanced/csv-parser.worker.ts +89 -0
- package/examples/advanced/nested-objects-example.js +306 -0
- package/examples/advanced/nested-objects-example.ts +306 -0
- package/examples/advanced/performance-optimization.js +504 -0
- package/examples/advanced/performance-optimization.ts +504 -0
- package/examples/advanced/run-demo-server.js +116 -0
- package/examples/advanced/run-demo-server.ts +116 -0
- package/examples/advanced/web-worker-usage.html +874 -0
- package/examples/async-multithreaded-example.ts +335 -0
- package/examples/cli-advanced-usage.md +288 -0
- package/examples/cli-batch-processing.ts +38 -0
- package/examples/cli-tool.js +0 -3
- package/examples/cli-tool.ts +183 -0
- package/examples/error-handling.js +21 -7
- package/examples/error-handling.ts +356 -0
- package/examples/express-api.js +0 -3
- package/examples/express-api.ts +164 -0
- package/examples/large-dataset-example.js +0 -3
- package/examples/large-dataset-example.ts +204 -0
- package/examples/ndjson-processing.js +1 -1
- package/examples/ndjson-processing.ts +456 -0
- package/examples/plugin-excel-exporter.js +3 -4
- package/examples/plugin-excel-exporter.ts +406 -0
- package/examples/react-integration.tsx +637 -0
- package/examples/schema-validation.ts +640 -0
- package/examples/simple-usage.js +254 -254
- package/examples/simple-usage.ts +194 -0
- package/examples/streaming-example.js +4 -5
- package/examples/streaming-example.ts +419 -0
- package/examples/web-workers-advanced.ts +28 -0
- package/index.d.ts +1 -3
- package/index.js +15 -1
- package/json-save.js +9 -3
- package/json-to-csv.js +168 -21
- package/package.json +69 -10
- package/plugins/express-middleware/README.md +21 -2
- package/plugins/express-middleware/example.js +3 -4
- package/plugins/express-middleware/example.ts +135 -0
- package/plugins/express-middleware/index.d.ts +1 -1
- package/plugins/express-middleware/index.js +270 -118
- package/plugins/express-middleware/index.ts +557 -0
- package/plugins/fastify-plugin/index.js +2 -4
- package/plugins/fastify-plugin/index.ts +443 -0
- package/plugins/hono/index.ts +226 -0
- package/plugins/nestjs/index.ts +201 -0
- package/plugins/nextjs-api/examples/ConverterComponent.tsx +386 -0
- package/plugins/nextjs-api/examples/api-convert.js +0 -2
- package/plugins/nextjs-api/examples/api-convert.ts +67 -0
- package/plugins/nextjs-api/index.tsx +339 -0
- package/plugins/nextjs-api/route.js +2 -3
- package/plugins/nextjs-api/route.ts +370 -0
- package/plugins/nuxt/index.ts +94 -0
- package/plugins/nuxt/runtime/composables/useJtcsv.ts +100 -0
- package/plugins/nuxt/runtime/plugin.ts +71 -0
- package/plugins/remix/index.js +1 -1
- package/plugins/remix/index.ts +260 -0
- package/plugins/sveltekit/index.js +1 -1
- package/plugins/sveltekit/index.ts +301 -0
- package/plugins/trpc/index.ts +267 -0
- package/src/browser/browser-functions.ts +402 -0
- package/src/browser/core.js +92 -0
- package/src/browser/core.ts +152 -0
- package/src/browser/csv-to-json-browser.d.ts +3 -0
- package/src/browser/csv-to-json-browser.js +36 -14
- package/src/browser/csv-to-json-browser.ts +264 -0
- package/src/browser/errors-browser.ts +303 -0
- package/src/browser/extensions/plugins.js +92 -0
- package/src/browser/extensions/plugins.ts +93 -0
- package/src/browser/extensions/workers.js +39 -0
- package/src/browser/extensions/workers.ts +39 -0
- package/src/browser/globals.d.ts +5 -0
- package/src/browser/index.ts +192 -0
- package/src/browser/json-to-csv-browser.d.ts +3 -0
- package/src/browser/json-to-csv-browser.js +13 -3
- package/src/browser/json-to-csv-browser.ts +262 -0
- package/src/browser/streams.js +12 -2
- package/src/browser/streams.ts +336 -0
- package/src/browser/workers/csv-parser.worker.ts +377 -0
- package/src/browser/workers/worker-pool.ts +548 -0
- package/src/core/delimiter-cache.js +22 -8
- package/src/core/delimiter-cache.ts +310 -0
- package/src/core/node-optimizations.ts +449 -0
- package/src/core/plugin-system.js +29 -11
- package/src/core/plugin-system.ts +400 -0
- package/src/core/transform-hooks.ts +558 -0
- package/src/engines/fast-path-engine-new.ts +347 -0
- package/src/engines/fast-path-engine.ts +854 -0
- package/src/errors.ts +72 -0
- package/src/formats/ndjson-parser.ts +469 -0
- package/src/formats/tsv-parser.ts +334 -0
- package/src/index-with-plugins.js +16 -9
- package/src/index-with-plugins.ts +395 -0
- package/src/types/index.ts +255 -0
- package/src/utils/bom-utils.js +259 -0
- package/src/utils/bom-utils.ts +373 -0
- package/src/utils/encoding-support.js +124 -0
- package/src/utils/encoding-support.ts +155 -0
- package/src/utils/schema-validator.js +19 -19
- package/src/utils/schema-validator.ts +819 -0
- package/src/utils/transform-loader.js +1 -1
- package/src/utils/transform-loader.ts +389 -0
- package/src/utils/zod-adapter.js +170 -0
- package/src/utils/zod-adapter.ts +280 -0
- package/src/web-server/index.js +10 -10
- package/src/web-server/index.ts +683 -0
- package/src/workers/csv-multithreaded.ts +310 -0
- package/src/workers/csv-parser.worker.ts +227 -0
- package/src/workers/worker-pool.ts +409 -0
- package/stream-csv-to-json.js +26 -8
- package/stream-json-to-csv.js +1 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker Pool для многопоточной обработки данных
|
|
3
|
+
*
|
|
4
|
+
* Система управления воркерами для параллельной обработки больших наборов данных
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
|
|
8
|
+
import { EventEmitter } from 'events';
|
|
9
|
+
import { WorkerTask, WorkerResult, WorkerPoolStats } from '../types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Класс для управления пулом воркеров
|
|
13
|
+
*/
|
|
14
|
+
export class WorkerPool extends EventEmitter {
|
|
15
|
+
private workers: Worker[] = [];
|
|
16
|
+
private idleWorkers: Worker[] = [];
|
|
17
|
+
private taskQueue: Array<{
|
|
18
|
+
task: WorkerTask;
|
|
19
|
+
resolve: (result: any) => void;
|
|
20
|
+
reject: (error: Error) => void;
|
|
21
|
+
}> = [];
|
|
22
|
+
|
|
23
|
+
private stats: WorkerPoolStats = {
|
|
24
|
+
totalWorkers: 0,
|
|
25
|
+
activeWorkers: 0,
|
|
26
|
+
idleWorkers: 0,
|
|
27
|
+
totalTasks: 0,
|
|
28
|
+
completedTasks: 0,
|
|
29
|
+
failedTasks: 0,
|
|
30
|
+
averageTaskDuration: 0
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
private taskDurations: number[] = [];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Создает пул воркеров
|
|
37
|
+
*
|
|
38
|
+
* @param workerCount - Количество воркеров (по умолчанию: количество ядер CPU - 1)
|
|
39
|
+
* @param workerScript - Путь к скрипту воркера
|
|
40
|
+
*/
|
|
41
|
+
constructor(
|
|
42
|
+
workerCount: number = Math.max(1, require('os').cpus().length - 1),
|
|
43
|
+
workerScript: string = __dirname + '/csv-parser.worker.js'
|
|
44
|
+
) {
|
|
45
|
+
super();
|
|
46
|
+
|
|
47
|
+
this.stats.totalWorkers = workerCount;
|
|
48
|
+
|
|
49
|
+
// Создаем воркеры
|
|
50
|
+
for (let i = 0; i < workerCount; i++) {
|
|
51
|
+
this.createWorker(workerScript, i);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Обновляем статистику каждые 5 секунд
|
|
55
|
+
setInterval(() => this.updateStats(), 5000);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Создает нового воркера
|
|
60
|
+
*/
|
|
61
|
+
private createWorker(workerScript: string, id: number): void {
|
|
62
|
+
const worker = new Worker(workerScript, {
|
|
63
|
+
workerData: { workerId: id }
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
worker.on('message', (result: WorkerResult) => {
|
|
67
|
+
this.handleWorkerResult(worker, result);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
worker.on('error', (error: Error) => {
|
|
71
|
+
this.handleWorkerError(worker, error);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
worker.on('exit', (code: number) => {
|
|
75
|
+
this.handleWorkerExit(worker, code);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
this.workers.push(worker);
|
|
79
|
+
this.idleWorkers.push(worker);
|
|
80
|
+
this.stats.idleWorkers++;
|
|
81
|
+
|
|
82
|
+
this.emit('workerCreated', { workerId: id });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Обрабатывает результат выполнения задачи воркером
|
|
87
|
+
*/
|
|
88
|
+
private handleWorkerResult(worker: Worker, result: WorkerResult): void {
|
|
89
|
+
// Находим задачу в очереди
|
|
90
|
+
const taskIndex = this.taskQueue.findIndex(item => item.task.id === result.id);
|
|
91
|
+
|
|
92
|
+
if (taskIndex !== -1) {
|
|
93
|
+
const { task, resolve, reject } = this.taskQueue[taskIndex];
|
|
94
|
+
|
|
95
|
+
// Удаляем задачу из очереди
|
|
96
|
+
this.taskQueue.splice(taskIndex, 1);
|
|
97
|
+
|
|
98
|
+
// Обновляем статистику
|
|
99
|
+
this.stats.completedTasks++;
|
|
100
|
+
this.taskDurations.push(result.duration);
|
|
101
|
+
this.updateAverageDuration();
|
|
102
|
+
|
|
103
|
+
if (result.error) {
|
|
104
|
+
this.stats.failedTasks++;
|
|
105
|
+
reject(result.error);
|
|
106
|
+
this.emit('taskFailed', { task, error: result.error });
|
|
107
|
+
} else {
|
|
108
|
+
resolve(result.result);
|
|
109
|
+
this.emit('taskCompleted', { task, result: result.result, duration: result.duration });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Возвращаем воркер в пул ожидания
|
|
114
|
+
this.idleWorkers.push(worker);
|
|
115
|
+
this.stats.activeWorkers--;
|
|
116
|
+
this.stats.idleWorkers++;
|
|
117
|
+
|
|
118
|
+
// Обрабатываем следующую задачу из очереди
|
|
119
|
+
this.processNextTask();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Обрабатывает ошибку воркера
|
|
124
|
+
*/
|
|
125
|
+
private handleWorkerError(worker: Worker, error: Error): void {
|
|
126
|
+
const workerIndex = this.workers.indexOf(worker);
|
|
127
|
+
const idleIndex = this.idleWorkers.indexOf(worker);
|
|
128
|
+
|
|
129
|
+
if (idleIndex !== -1) {
|
|
130
|
+
this.idleWorkers.splice(idleIndex, 1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (workerIndex !== -1) {
|
|
134
|
+
this.workers.splice(workerIndex, 1);
|
|
135
|
+
this.stats.totalWorkers--;
|
|
136
|
+
this.stats.idleWorkers--;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.emit('workerError', { worker, error });
|
|
140
|
+
|
|
141
|
+
// Перезапускаем воркер
|
|
142
|
+
setTimeout(() => {
|
|
143
|
+
this.createWorker(worker.threadId.toString(), this.workers.length);
|
|
144
|
+
}, 1000);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Обрабатывает завершение работы воркера
|
|
149
|
+
*/
|
|
150
|
+
private handleWorkerExit(worker: Worker, code: number): void {
|
|
151
|
+
const workerIndex = this.workers.indexOf(worker);
|
|
152
|
+
const idleIndex = this.idleWorkers.indexOf(worker);
|
|
153
|
+
|
|
154
|
+
if (idleIndex !== -1) {
|
|
155
|
+
this.idleWorkers.splice(idleIndex, 1);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (workerIndex !== -1) {
|
|
159
|
+
this.workers.splice(workerIndex, 1);
|
|
160
|
+
this.stats.totalWorkers--;
|
|
161
|
+
this.stats.idleWorkers--;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this.emit('workerExit', { worker, code });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Обрабатывает следующую задачу из очереди
|
|
169
|
+
*/
|
|
170
|
+
private processNextTask(): void {
|
|
171
|
+
if (this.taskQueue.length === 0 || this.idleWorkers.length === 0) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const nextTask = this.taskQueue[0];
|
|
176
|
+
const worker = this.idleWorkers.shift()!;
|
|
177
|
+
|
|
178
|
+
// Удаляем задачу из начала очереди
|
|
179
|
+
this.taskQueue.shift();
|
|
180
|
+
|
|
181
|
+
// Отправляем задачу воркеру
|
|
182
|
+
worker.postMessage(nextTask.task);
|
|
183
|
+
|
|
184
|
+
this.stats.activeWorkers++;
|
|
185
|
+
this.stats.idleWorkers--;
|
|
186
|
+
this.stats.totalTasks++;
|
|
187
|
+
|
|
188
|
+
this.emit('taskStarted', { task: nextTask.task, workerId: worker.threadId });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Обновляет среднюю продолжительность выполнения задач
|
|
193
|
+
*/
|
|
194
|
+
private updateAverageDuration(): void {
|
|
195
|
+
if (this.taskDurations.length === 0) {
|
|
196
|
+
this.stats.averageTaskDuration = 0;
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const sum = this.taskDurations.reduce((a, b) => a + b, 0);
|
|
201
|
+
this.stats.averageTaskDuration = sum / this.taskDurations.length;
|
|
202
|
+
|
|
203
|
+
// Ограничиваем историю до последних 100 задач
|
|
204
|
+
if (this.taskDurations.length > 100) {
|
|
205
|
+
this.taskDurations = this.taskDurations.slice(-100);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Обновляет статистику
|
|
211
|
+
*/
|
|
212
|
+
private updateStats(): void {
|
|
213
|
+
// Обновляем использование памяти
|
|
214
|
+
if (typeof process !== 'undefined' && process.memoryUsage) {
|
|
215
|
+
this.stats.memoryUsage = process.memoryUsage();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
this.emit('statsUpdated', this.stats);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Выполняет задачу через пул воркеров
|
|
223
|
+
*
|
|
224
|
+
* @param task - Задача для выполнения
|
|
225
|
+
* @returns Promise с результатом
|
|
226
|
+
*/
|
|
227
|
+
async executeTask<T = any, R = any>(task: WorkerTask<T, R>): Promise<R> {
|
|
228
|
+
return new Promise((resolve, reject) => {
|
|
229
|
+
// Добавляем ID задачи если не указан
|
|
230
|
+
if (!task.id) {
|
|
231
|
+
task.id = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Добавляем задачу в очередь
|
|
235
|
+
this.taskQueue.push({ task, resolve, reject });
|
|
236
|
+
|
|
237
|
+
// Пытаемся обработать задачу сразу
|
|
238
|
+
this.processNextTask();
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Выполняет несколько задач параллельно
|
|
244
|
+
*
|
|
245
|
+
* @param tasks - Массив задач
|
|
246
|
+
* @param concurrency - Максимальное количество параллельных задач
|
|
247
|
+
* @returns Promise с результатами
|
|
248
|
+
*/
|
|
249
|
+
async executeTasks<T = any, R = any>(
|
|
250
|
+
tasks: WorkerTask<T, R>[],
|
|
251
|
+
concurrency?: number
|
|
252
|
+
): Promise<R[]> {
|
|
253
|
+
const results: R[] = [];
|
|
254
|
+
const errors: Error[] = [];
|
|
255
|
+
|
|
256
|
+
// Ограничиваем параллелизм если указано
|
|
257
|
+
const maxConcurrency = concurrency || this.workers.length;
|
|
258
|
+
const taskChunks = this.chunkArray(tasks, maxConcurrency);
|
|
259
|
+
|
|
260
|
+
for (const chunk of taskChunks) {
|
|
261
|
+
const chunkPromises = chunk.map(task => this.executeTask(task));
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const chunkResults = await Promise.all(chunkPromises);
|
|
265
|
+
results.push(...chunkResults);
|
|
266
|
+
} catch (error: any) {
|
|
267
|
+
errors.push(error);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (errors.length > 0) {
|
|
272
|
+
throw new AggregateError(errors, `Failed to execute ${errors.length} tasks`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return results;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Разделяет массив на чанки
|
|
280
|
+
*/
|
|
281
|
+
private chunkArray<T>(array: T[], chunkSize: number): T[][] {
|
|
282
|
+
const chunks: T[][] = [];
|
|
283
|
+
|
|
284
|
+
for (let i = 0; i < array.length; i += chunkSize) {
|
|
285
|
+
chunks.push(array.slice(i, i + chunkSize));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return chunks;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Возвращает статистику пула
|
|
293
|
+
*/
|
|
294
|
+
getStats(): WorkerPoolStats {
|
|
295
|
+
return { ...this.stats };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Останавливает пул воркеров
|
|
300
|
+
*/
|
|
301
|
+
async shutdown(): Promise<void> {
|
|
302
|
+
// Завершаем все воркеры
|
|
303
|
+
const terminationPromises = this.workers.map(worker => {
|
|
304
|
+
return worker.terminate();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
await Promise.all(terminationPromises);
|
|
308
|
+
|
|
309
|
+
this.workers = [];
|
|
310
|
+
this.idleWorkers = [];
|
|
311
|
+
this.taskQueue = [];
|
|
312
|
+
|
|
313
|
+
this.emit('shutdown');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Перезапускает пул воркеров
|
|
318
|
+
*/
|
|
319
|
+
async restart(): Promise<void> {
|
|
320
|
+
await this.shutdown();
|
|
321
|
+
|
|
322
|
+
// Создаем новых воркеров
|
|
323
|
+
const workerCount = this.stats.totalWorkers;
|
|
324
|
+
for (let i = 0; i < workerCount; i++) {
|
|
325
|
+
this.createWorker(__dirname + '/csv-parser.worker.js', i);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
this.emit('restart');
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Утилитарные функции для работы с воркерами
|
|
334
|
+
*/
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Создает задачу для воркера
|
|
338
|
+
*/
|
|
339
|
+
export function createWorkerTask<T = any, R = any>(
|
|
340
|
+
type: string,
|
|
341
|
+
data: T,
|
|
342
|
+
options?: Record<string, any>
|
|
343
|
+
): WorkerTask<T, R> {
|
|
344
|
+
return {
|
|
345
|
+
id: `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
346
|
+
type,
|
|
347
|
+
data,
|
|
348
|
+
options
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Создает результат выполнения задачи
|
|
354
|
+
*/
|
|
355
|
+
export function createWorkerResult<R = any>(
|
|
356
|
+
id: string,
|
|
357
|
+
result: R,
|
|
358
|
+
duration: number,
|
|
359
|
+
error?: Error
|
|
360
|
+
): WorkerResult<R> {
|
|
361
|
+
return {
|
|
362
|
+
id,
|
|
363
|
+
result,
|
|
364
|
+
error,
|
|
365
|
+
duration
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Глобальный экземпляр пула воркеров (синглтон)
|
|
371
|
+
*/
|
|
372
|
+
let globalWorkerPool: WorkerPool | null = null;
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Возвращает глобальный экземпляр пула воркеров
|
|
376
|
+
*/
|
|
377
|
+
export function getWorkerPool(
|
|
378
|
+
workerCount?: number,
|
|
379
|
+
workerScript?: string
|
|
380
|
+
): WorkerPool {
|
|
381
|
+
if (!globalWorkerPool) {
|
|
382
|
+
globalWorkerPool = new WorkerPool(workerCount, workerScript);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return globalWorkerPool;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Останавливает глобальный пул воркеров
|
|
390
|
+
*/
|
|
391
|
+
export async function shutdownWorkerPool(): Promise<void> {
|
|
392
|
+
if (globalWorkerPool) {
|
|
393
|
+
await globalWorkerPool.shutdown();
|
|
394
|
+
globalWorkerPool = null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Класс для агрегированных ошибок
|
|
400
|
+
*/
|
|
401
|
+
export class AggregateError extends Error {
|
|
402
|
+
errors: Error[];
|
|
403
|
+
|
|
404
|
+
constructor(errors: Error[], message: string) {
|
|
405
|
+
super(message);
|
|
406
|
+
this.name = 'AggregateError';
|
|
407
|
+
this.errors = errors;
|
|
408
|
+
}
|
|
409
|
+
}
|
package/stream-csv-to-json.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
/**
|
|
2
3
|
* Stream CSV to JSON Converter - Node.js Module
|
|
3
4
|
*
|
|
@@ -22,6 +23,7 @@ const { pipeline } = require('stream/promises');
|
|
|
22
23
|
|
|
23
24
|
// Import schema validator from utils
|
|
24
25
|
const { createSchemaValidators } = require('./src/utils/schema-validator');
|
|
26
|
+
const { createBomStripStream: _createBomStripStream, detectBom: _detectBom, stripBomFromString } = require('./src/utils/bom-utils');
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Creates a transform stream that converts CSV chunks to JSON objects
|
|
@@ -65,7 +67,8 @@ function createCsvToJsonStream(options = {}) {
|
|
|
65
67
|
parseBooleans = false,
|
|
66
68
|
maxRows = Infinity,
|
|
67
69
|
transform = null,
|
|
68
|
-
schema = null
|
|
70
|
+
schema = null,
|
|
71
|
+
stripBom = true // New option: strip BOM by default
|
|
69
72
|
} = opts;
|
|
70
73
|
|
|
71
74
|
// Validate options
|
|
@@ -180,11 +183,18 @@ function createCsvToJsonStream(options = {}) {
|
|
|
180
183
|
}
|
|
181
184
|
|
|
182
185
|
// Parse numbers
|
|
183
|
-
if (parseNumbers
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
if (parseNumbers) {
|
|
187
|
+
// Fast numeric detection: check first character and use parseFloat
|
|
188
|
+
const trimmed = result.trim();
|
|
189
|
+
const firstChar = trimmed.charAt(0);
|
|
190
|
+
if ((firstChar >= '0' && firstChar <= '9') || firstChar === '-' || firstChar === '.') {
|
|
191
|
+
const num = parseFloat(trimmed);
|
|
192
|
+
if (!isNaN(num) && isFinite(num)) {
|
|
193
|
+
// Ensure the whole string represents the same number (no extra characters)
|
|
194
|
+
if (String(num) === trimmed || (trimmed.includes('.') && !isNaN(Number(trimmed)))) {
|
|
195
|
+
return num;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
188
198
|
}
|
|
189
199
|
}
|
|
190
200
|
|
|
@@ -340,14 +350,20 @@ function createCsvToJsonStream(options = {}) {
|
|
|
340
350
|
}
|
|
341
351
|
};
|
|
342
352
|
|
|
343
|
-
|
|
353
|
+
let bomStripped = false;
|
|
354
|
+
const csvParser = new Transform({
|
|
344
355
|
objectMode: true,
|
|
345
356
|
writableObjectMode: false,
|
|
346
357
|
readableObjectMode: true,
|
|
347
358
|
|
|
348
359
|
transform(chunk, encoding, callback) {
|
|
349
360
|
try {
|
|
350
|
-
|
|
361
|
+
let chunkStr = chunk.toString();
|
|
362
|
+
// Strip BOM from first chunk if enabled
|
|
363
|
+
if (stripBom && !bomStripped) {
|
|
364
|
+
chunkStr = stripBomFromString(chunkStr);
|
|
365
|
+
bomStripped = true;
|
|
366
|
+
}
|
|
351
367
|
buffer += chunkStr;
|
|
352
368
|
|
|
353
369
|
// Process complete lines
|
|
@@ -408,6 +424,8 @@ function createCsvToJsonStream(options = {}) {
|
|
|
408
424
|
}
|
|
409
425
|
}
|
|
410
426
|
});
|
|
427
|
+
|
|
428
|
+
return csvParser;
|
|
411
429
|
}, 'STREAM_CREATION_ERROR', { function: 'createCsvToJsonStream' });
|
|
412
430
|
}
|
|
413
431
|
|
package/stream-json-to-csv.js
CHANGED