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,504 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Example: Performance Optimization Patterns
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates various performance optimization techniques with jtcsv
|
|
5
|
+
* including caching, streaming, batch processing, and memory management.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { csvToJson, jsonToCsv, createCsvToJsonStream, createJsonToCsvStream } = require('../../index.js');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const { pipeline } = require('stream/promises');
|
|
11
|
+
const { performance, PerformanceObserver } = require('perf_hooks');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Example 1: Delimiter Caching for Repeated Operations
|
|
15
|
+
*
|
|
16
|
+
* Scenario: Processing multiple CSV files with similar structure
|
|
17
|
+
*/
|
|
18
|
+
async function exampleDelimiterCaching() {
|
|
19
|
+
console.log('=== Example 1: Delimiter Caching ===\n');
|
|
20
|
+
|
|
21
|
+
// Create multiple CSV files with different delimiters
|
|
22
|
+
const testFiles = [
|
|
23
|
+
{ name: 'semicolon.csv', content: 'id;name;age\n1;Alice;30\n2;Bob;25\n3;Charlie;35', delimiter: ';' },
|
|
24
|
+
{ name: 'comma.csv', content: 'id,name,age\n1,Alice,30\n2,Bob,25\n3,Charlie,35', delimiter: ',' },
|
|
25
|
+
{ name: 'pipe.csv', content: 'id|name|age\n1|Alice|30\n2|Bob|25\n3|Charlie|35', delimiter: '|' },
|
|
26
|
+
{ name: 'tab.csv', content: 'id\tname\tage\n1\tAlice\t30\n2\tBob\t25\n3\tCharlie\t35', delimiter: '\t' }
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
console.log('Processing without caching:');
|
|
30
|
+
const timesWithoutCache = [];
|
|
31
|
+
|
|
32
|
+
for (const file of testFiles) {
|
|
33
|
+
const start = performance.now();
|
|
34
|
+
|
|
35
|
+
// Process without cache (auto-detection each time)
|
|
36
|
+
const data = csvToJson(file.content, {
|
|
37
|
+
autoDetect: true,
|
|
38
|
+
useCache: false, // Explicitly disable cache
|
|
39
|
+
parseNumbers: true
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const end = performance.now();
|
|
43
|
+
timesWithoutCache.push(end - start);
|
|
44
|
+
|
|
45
|
+
console.log(` ${file.name}: ${(end - start).toFixed(2)}ms (detected: ${data.length} rows)`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log('\nProcessing with caching:');
|
|
49
|
+
const timesWithCache = [];
|
|
50
|
+
|
|
51
|
+
for (const file of testFiles) {
|
|
52
|
+
const start = performance.now();
|
|
53
|
+
|
|
54
|
+
// Process with cache (reuses delimiter detection)
|
|
55
|
+
const data = csvToJson(file.content, {
|
|
56
|
+
autoDetect: true,
|
|
57
|
+
useCache: true, // Enable caching
|
|
58
|
+
parseNumbers: true
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const end = performance.now();
|
|
62
|
+
timesWithCache.push(end - start);
|
|
63
|
+
|
|
64
|
+
console.log(` ${file.name}: ${(end - start).toFixed(2)}ms (detected: ${data.length} rows)`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const avgWithoutCache = timesWithoutCache.reduce((a, b) => a + b, 0) / timesWithoutCache.length;
|
|
68
|
+
const avgWithCache = timesWithCache.reduce((a, b) => a + b, 0) / timesWithCache.length;
|
|
69
|
+
const improvement = ((avgWithoutCache - avgWithCache) / avgWithoutCache * 100).toFixed(1);
|
|
70
|
+
|
|
71
|
+
console.log(`\nPerformance Improvement: ${improvement}% faster with caching`);
|
|
72
|
+
console.log(`Average time without cache: ${avgWithoutCache.toFixed(2)}ms`);
|
|
73
|
+
console.log(`Average time with cache: ${avgWithCache.toFixed(2)}ms`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Example 2: Memory-Efficient Streaming with Backpressure Control
|
|
78
|
+
*
|
|
79
|
+
* Scenario: Processing very large files with controlled memory usage
|
|
80
|
+
*/
|
|
81
|
+
async function exampleMemoryEfficientStreaming() {
|
|
82
|
+
console.log('\n\n=== Example 2: Memory-Efficient Streaming ===\n');
|
|
83
|
+
|
|
84
|
+
// Create a large CSV file (in memory for demonstration)
|
|
85
|
+
const rowCount = 100000;
|
|
86
|
+
const headers = Array.from({ length: 20 }, (_, i) => `column${i + 1}`);
|
|
87
|
+
const largeCsv = headers.join(',') + '\n' +
|
|
88
|
+
Array.from({ length: rowCount }, (_, rowIndex) =>
|
|
89
|
+
headers.map((_, colIndex) => `value${rowIndex}_${colIndex}`).join(',')
|
|
90
|
+
).join('\n');
|
|
91
|
+
|
|
92
|
+
console.log(`Generated ${rowCount.toLocaleString()} rows with ${headers.length} columns`);
|
|
93
|
+
console.log('Approximate size:', Math.round(largeCsv.length / 1024 / 1024 * 100) / 100, 'MB');
|
|
94
|
+
|
|
95
|
+
// Method 1: In-memory processing (high memory usage)
|
|
96
|
+
console.log('\nMethod 1: In-memory processing');
|
|
97
|
+
const memoryStart = performance.now();
|
|
98
|
+
const memoryUsageBefore = process.memoryUsage();
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const allData = csvToJson(largeCsv, {
|
|
102
|
+
hasHeaders: true,
|
|
103
|
+
maxRows: 10000 // Limit for demonstration
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const memoryUsageAfter = process.memoryUsage();
|
|
107
|
+
const memoryEnd = performance.now();
|
|
108
|
+
|
|
109
|
+
console.log(` Time: ${(memoryEnd - memoryStart).toFixed(2)}ms`);
|
|
110
|
+
console.log(` Memory increase: ${Math.round((memoryUsageAfter.heapUsed - memoryUsageBefore.heapUsed) / 1024 / 1024)} MB`);
|
|
111
|
+
console.log(` Rows processed: ${allData.length}`);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.log(` Error: ${error.message} (likely out of memory)`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Method 2: Streaming with backpressure control
|
|
117
|
+
console.log('\nMethod 2: Streaming with backpressure control');
|
|
118
|
+
const streamStart = performance.now();
|
|
119
|
+
const streamMemoryBefore = process.memoryUsage();
|
|
120
|
+
|
|
121
|
+
let streamRowCount = 0;
|
|
122
|
+
let batchCount = 0;
|
|
123
|
+
const batchSize = 1000;
|
|
124
|
+
|
|
125
|
+
const { Readable, Transform, Writable } = require('stream');
|
|
126
|
+
|
|
127
|
+
// Create readable stream from CSV string
|
|
128
|
+
const readable = new Readable({
|
|
129
|
+
read() {
|
|
130
|
+
this.push(largeCsv);
|
|
131
|
+
this.push(null); // End stream
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Create CSV parser stream with controlled batch processing
|
|
136
|
+
const csvStream = createCsvToJsonStream({
|
|
137
|
+
hasHeaders: true
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Custom transform to control backpressure
|
|
141
|
+
const batchProcessor = new Transform({
|
|
142
|
+
objectMode: true,
|
|
143
|
+
highWaterMark: 100, // Control memory by limiting buffer size
|
|
144
|
+
|
|
145
|
+
transform(row, encoding, callback) {
|
|
146
|
+
streamRowCount++;
|
|
147
|
+
|
|
148
|
+
// Process in batches
|
|
149
|
+
if (streamRowCount % batchSize === 0) {
|
|
150
|
+
batchCount++;
|
|
151
|
+
|
|
152
|
+
// Simulate batch processing (e.g., database insert)
|
|
153
|
+
// In real scenario, this could be async database operation
|
|
154
|
+
|
|
155
|
+
// Control backpressure: pause if processing is slow
|
|
156
|
+
if (batchCount % 10 === 0) {
|
|
157
|
+
setTimeout(() => {
|
|
158
|
+
callback(null, row);
|
|
159
|
+
}, 10); // Simulate slow processing
|
|
160
|
+
} else {
|
|
161
|
+
callback(null, row);
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
callback(null, row);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Monitor memory during streaming
|
|
170
|
+
const memorySamples = [];
|
|
171
|
+
const memoryMonitor = setInterval(() => {
|
|
172
|
+
memorySamples.push(process.memoryUsage().heapUsed);
|
|
173
|
+
}, 100);
|
|
174
|
+
|
|
175
|
+
// Collector that does nothing (just counts)
|
|
176
|
+
const collector = new Writable({
|
|
177
|
+
objectMode: true,
|
|
178
|
+
write(row, encoding, callback) {
|
|
179
|
+
callback();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
await pipeline(readable, csvStream, batchProcessor, collector);
|
|
185
|
+
|
|
186
|
+
clearInterval(memoryMonitor);
|
|
187
|
+
const streamEnd = performance.now();
|
|
188
|
+
const streamMemoryAfter = process.memoryUsage();
|
|
189
|
+
|
|
190
|
+
const peakMemory = Math.max(...memorySamples);
|
|
191
|
+
const avgMemory = memorySamples.reduce((a, b) => a + b, 0) / memorySamples.length;
|
|
192
|
+
|
|
193
|
+
console.log(` Time: ${(streamEnd - streamStart).toFixed(2)}ms`);
|
|
194
|
+
console.log(` Peak memory: ${Math.round(peakMemory / 1024 / 1024)} MB`);
|
|
195
|
+
console.log(` Average memory: ${Math.round(avgMemory / 1024 / 1024)} MB`);
|
|
196
|
+
console.log(` Final memory: ${Math.round(streamMemoryAfter.heapUsed / 1024 / 1024)} MB`);
|
|
197
|
+
console.log(` Memory increase: ${Math.round((streamMemoryAfter.heapUsed - streamMemoryBefore.heapUsed) / 1024 / 1024)} MB`);
|
|
198
|
+
console.log(` Rows processed: ${streamRowCount.toLocaleString()}`);
|
|
199
|
+
console.log(` Batches: ${batchCount}`);
|
|
200
|
+
|
|
201
|
+
} catch (error) {
|
|
202
|
+
clearInterval(memoryMonitor);
|
|
203
|
+
console.log(` Error: ${error.message}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Example 3: Parallel Processing with Worker Threads
|
|
209
|
+
*
|
|
210
|
+
* Scenario: CPU-intensive transformations on large datasets
|
|
211
|
+
*/
|
|
212
|
+
async function exampleParallelProcessing() {
|
|
213
|
+
console.log('\n\n=== Example 3: Parallel Processing ===\n');
|
|
214
|
+
|
|
215
|
+
// Generate sample data
|
|
216
|
+
const rowCount = 50000;
|
|
217
|
+
const data = Array.from({ length: rowCount }, (_, i) => ({
|
|
218
|
+
id: i + 1,
|
|
219
|
+
value: Math.random() * 1000,
|
|
220
|
+
category: `CAT${Math.floor(Math.random() * 10) + 1}`,
|
|
221
|
+
timestamp: new Date(Date.now() - Math.random() * 10000000000).toISOString()
|
|
222
|
+
}));
|
|
223
|
+
|
|
224
|
+
console.log(`Generated ${rowCount.toLocaleString()} rows for processing`);
|
|
225
|
+
|
|
226
|
+
// CPU-intensive transformation function
|
|
227
|
+
function complexTransformation(row) {
|
|
228
|
+
// Simulate CPU-intensive operations
|
|
229
|
+
const result = { ...row };
|
|
230
|
+
|
|
231
|
+
// Multiple mathematical operations
|
|
232
|
+
for (let i = 0; i < 100; i++) {
|
|
233
|
+
result.value = Math.sin(result.value) * Math.cos(result.value) * Math.tan(result.value);
|
|
234
|
+
result.value = Math.sqrt(Math.abs(result.value)) * Math.log(Math.abs(result.value) + 1);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// String manipulations
|
|
238
|
+
result.category_code = result.category.split('').map(c => c.charCodeAt(0)).join('-');
|
|
239
|
+
result.hash = require('crypto').createHash('md5').update(JSON.stringify(result)).digest('hex').substring(0, 8);
|
|
240
|
+
|
|
241
|
+
// Date calculations
|
|
242
|
+
const date = new Date(result.timestamp);
|
|
243
|
+
result.year = date.getFullYear();
|
|
244
|
+
result.quarter = Math.floor(date.getMonth() / 3) + 1;
|
|
245
|
+
result.day_of_week = date.getDay();
|
|
246
|
+
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Method 1: Sequential processing
|
|
251
|
+
console.log('Method 1: Sequential processing');
|
|
252
|
+
const sequentialStart = performance.now();
|
|
253
|
+
|
|
254
|
+
const sequentialResults = data.map(row => complexTransformation(row));
|
|
255
|
+
|
|
256
|
+
const sequentialEnd = performance.now();
|
|
257
|
+
console.log(` Time: ${(sequentialEnd - sequentialStart).toFixed(2)}ms`);
|
|
258
|
+
console.log(` Rate: ${(rowCount / ((sequentialEnd - sequentialStart) / 1000)).toFixed(0)} rows/second`);
|
|
259
|
+
|
|
260
|
+
// Method 2: Batch processing with setImmediate (cooperative multitasking)
|
|
261
|
+
console.log('\nMethod 2: Batch processing with cooperative multitasking');
|
|
262
|
+
const batchStart = performance.now();
|
|
263
|
+
|
|
264
|
+
const batchResults = [];
|
|
265
|
+
const batchSize = 1000;
|
|
266
|
+
let batchIndex = 0;
|
|
267
|
+
|
|
268
|
+
function processBatch() {
|
|
269
|
+
const startIdx = batchIndex * batchSize;
|
|
270
|
+
const endIdx = Math.min(startIdx + batchSize, data.length);
|
|
271
|
+
|
|
272
|
+
for (let i = startIdx; i < endIdx; i++) {
|
|
273
|
+
batchResults.push(complexTransformation(data[i]));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
batchIndex++;
|
|
277
|
+
|
|
278
|
+
if (batchIndex * batchSize < data.length) {
|
|
279
|
+
// Yield to event loop between batches
|
|
280
|
+
setImmediate(processBatch);
|
|
281
|
+
} else {
|
|
282
|
+
const batchEnd = performance.now();
|
|
283
|
+
console.log(` Time: ${(batchEnd - batchStart).toFixed(2)}ms`);
|
|
284
|
+
console.log(` Rate: ${(rowCount / ((batchEnd - batchStart) / 1000)).toFixed(0)} rows/second`);
|
|
285
|
+
console.log(` Batches: ${batchIndex}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Start batch processing
|
|
290
|
+
await new Promise(resolve => {
|
|
291
|
+
processBatch();
|
|
292
|
+
// Simple check for completion (in real scenario, use proper signaling)
|
|
293
|
+
const checkInterval = setInterval(() => {
|
|
294
|
+
if (batchResults.length === data.length) {
|
|
295
|
+
clearInterval(checkInterval);
|
|
296
|
+
resolve();
|
|
297
|
+
}
|
|
298
|
+
}, 10);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Method 3: Using jtcsv with transform hooks for parallel-like processing
|
|
302
|
+
console.log('\nMethod 3: jtcsv with optimized transform hooks');
|
|
303
|
+
|
|
304
|
+
// Convert data to CSV for processing
|
|
305
|
+
const csvData = jsonToCsv(data);
|
|
306
|
+
|
|
307
|
+
const transformStart = performance.now();
|
|
308
|
+
|
|
309
|
+
const transformedData = csvToJson(csvData, {
|
|
310
|
+
hasHeaders: true,
|
|
311
|
+
parseNumbers: true,
|
|
312
|
+
hooks: {
|
|
313
|
+
perRow: (row, index) => {
|
|
314
|
+
// Process every 10th row with intensive transformation
|
|
315
|
+
if (index % 10 === 0) {
|
|
316
|
+
return complexTransformation(row);
|
|
317
|
+
}
|
|
318
|
+
// Simple transformation for other rows
|
|
319
|
+
return {
|
|
320
|
+
...row,
|
|
321
|
+
processed: true,
|
|
322
|
+
index
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const transformEnd = performance.now();
|
|
329
|
+
console.log(` Time: ${(transformEnd - transformStart).toFixed(2)}ms`);
|
|
330
|
+
console.log(` Rate: ${(rowCount / ((transformEnd - transformStart) / 1000)).toFixed(0)} rows/second`);
|
|
331
|
+
console.log(` Rows processed: ${transformedData.length}`);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Example 4: Optimized File I/O Patterns
|
|
336
|
+
*
|
|
337
|
+
* Scenario: Efficient reading/writing of large CSV files
|
|
338
|
+
*/
|
|
339
|
+
async function exampleOptimizedFileIO() {
|
|
340
|
+
console.log('\n\n=== Example 4: Optimized File I/O Patterns ===\n');
|
|
341
|
+
|
|
342
|
+
const tempDir = './temp_benchmark';
|
|
343
|
+
if (!fs.existsSync(tempDir)) {
|
|
344
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Create a test file
|
|
348
|
+
const testFile = `${tempDir}/test_data.csv`;
|
|
349
|
+
const rowCount = 100000;
|
|
350
|
+
const columnCount = 15;
|
|
351
|
+
|
|
352
|
+
console.log(`Creating test file with ${rowCount.toLocaleString()} rows...`);
|
|
353
|
+
|
|
354
|
+
const headers = Array.from({ length: columnCount }, (_, i) => `col${i + 1}`);
|
|
355
|
+
const writeStream = fs.createWriteStream(testFile, { encoding: 'utf8' });
|
|
356
|
+
|
|
357
|
+
writeStream.write(headers.join(',') + '\n');
|
|
358
|
+
|
|
359
|
+
for (let i = 0; i < rowCount; i++) {
|
|
360
|
+
const row = headers.map((_, j) => `value${i}_${j}_${Math.random().toString(36).substring(7)}`);
|
|
361
|
+
if (!writeStream.write(row.join(',') + '\n')) {
|
|
362
|
+
await new Promise(resolve => writeStream.once('drain', resolve));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (i % 10000 === 0 && i > 0) {
|
|
366
|
+
console.log(` Written ${i.toLocaleString()} rows...`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
writeStream.end();
|
|
371
|
+
await new Promise(resolve => writeStream.once('close', resolve));
|
|
372
|
+
|
|
373
|
+
const fileSize = fs.statSync(testFile).size;
|
|
374
|
+
console.log(`File created: ${Math.round(fileSize / 1024 / 1024 * 100) / 100} MB`);
|
|
375
|
+
|
|
376
|
+
// Pattern 1: Read entire file into memory
|
|
377
|
+
console.log('\nPattern 1: Read entire file into memory');
|
|
378
|
+
const pattern1Start = performance.now();
|
|
379
|
+
const memoryBefore1 = process.memoryUsage();
|
|
380
|
+
|
|
381
|
+
const fileContent = fs.readFileSync(testFile, 'utf8');
|
|
382
|
+
const data1 = csvToJson(fileContent, { hasHeaders: true });
|
|
383
|
+
|
|
384
|
+
const pattern1End = performance.now();
|
|
385
|
+
const memoryAfter1 = process.memoryUsage();
|
|
386
|
+
|
|
387
|
+
console.log(` Read time: ${(pattern1End - pattern1Start).toFixed(2)}ms`);
|
|
388
|
+
console.log(` Memory used: ${Math.round((memoryAfter1.heapUsed - memoryBefore1.heapUsed) / 1024 / 1024)} MB`);
|
|
389
|
+
console.log(` Rows: ${data1.length}`);
|
|
390
|
+
|
|
391
|
+
// Pattern 2: Streaming with file handle
|
|
392
|
+
console.log('\nPattern 2: Streaming with file handle');
|
|
393
|
+
const pattern2Start = performance.now();
|
|
394
|
+
const memoryBefore2 = process.memoryUsage();
|
|
395
|
+
|
|
396
|
+
const readStream = fs.createReadStream(testFile, {
|
|
397
|
+
encoding: 'utf8',
|
|
398
|
+
highWaterMark: 64 * 1024 // 64KB chunks for optimal disk I/O
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
const csvStream = createCsvToJsonStream({ hasHeaders: true });
|
|
402
|
+
const rowCounts2 = { count: 0 };
|
|
403
|
+
|
|
404
|
+
const countingStream = new (require('stream').Writable)({
|
|
405
|
+
objectMode: true,
|
|
406
|
+
write(row, encoding, callback) {
|
|
407
|
+
rowCounts2.count++;
|
|
408
|
+
callback();
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
await pipeline(readStream, csvStream, countingStream);
|
|
413
|
+
|
|
414
|
+
const pattern2End = performance.now();
|
|
415
|
+
const memoryAfter2 = process.memoryUsage();
|
|
416
|
+
|
|
417
|
+
console.log(` Read time: ${(pattern2End - pattern2Start).toFixed(2)}ms`);
|
|
418
|
+
console.log(` Memory used: ${Math.round((memoryAfter2.heapUsed - memoryBefore2.heapUsed) / 1024 / 1024)} MB`);
|
|
419
|
+
console.log(` Rows: ${rowCounts2.count.toLocaleString()}`);
|
|
420
|
+
|
|
421
|
+
// Pattern 3: Memory-mapped reading (simulated with buffers)
|
|
422
|
+
console.log('\nPattern 3: Memory-mapped reading (simulated)');
|
|
423
|
+
const pattern3Start = performance.now();
|
|
424
|
+
const memoryBefore3 = process.memoryUsage();
|
|
425
|
+
|
|
426
|
+
// Read file in chunks and process incrementally
|
|
427
|
+
const chunkSize = 1024 * 1024; // 1MB chunks
|
|
428
|
+
const fd = fs.openSync(testFile, 'r');
|
|
429
|
+
let position = 0;
|
|
430
|
+
const buffer = Buffer.alloc(chunkSize);
|
|
431
|
+
let leftover = '';
|
|
432
|
+
let rowCount3 = 0;
|
|
433
|
+
|
|
434
|
+
while (true) {
|
|
435
|
+
const bytesRead = fs.readSync(fd, buffer, 0, chunkSize, position);
|
|
436
|
+
if (bytesRead === 0) {
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const chunk = leftover + buffer.toString('utf8', 0, bytesRead);
|
|
441
|
+
const lines = chunk.split('\n');
|
|
442
|
+
|
|
443
|
+
// Last line might be incomplete
|
|
444
|
+
leftover = lines.pop() || '';
|
|
445
|
+
|
|
446
|
+
// Count complete lines (excluding header)
|
|
447
|
+
rowCount3 += lines.length > 0 ? lines.length - 1 : 0;
|
|
448
|
+
|
|
449
|
+
position += bytesRead;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
fs.closeSync(fd);
|
|
453
|
+
|
|
454
|
+
const pattern3End = performance.now();
|
|
455
|
+
const memoryAfter3 = process.memoryUsage();
|
|
456
|
+
|
|
457
|
+
console.log(` Read time: ${(pattern3End - pattern3Start).toFixed(2)}ms`);
|
|
458
|
+
console.log(` Memory used: ${Math.round((memoryAfter3.heapUsed - memoryBefore3.heapUsed) / 1024 / 1024)} MB`);
|
|
459
|
+
console.log(` Rows: ${rowCount3.toLocaleString()}`);
|
|
460
|
+
|
|
461
|
+
// Cleanup
|
|
462
|
+
fs.unlinkSync(testFile);
|
|
463
|
+
fs.rmdirSync(tempDir);
|
|
464
|
+
|
|
465
|
+
console.log('\nSummary:');
|
|
466
|
+
console.log(' Pattern 1 (in-memory): Fastest but highest memory usage');
|
|
467
|
+
console.log(' Pattern 2 (streaming): Good balance of speed and memory');
|
|
468
|
+
console.log(' Pattern 3 (chunked): Lowest memory but more complex');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Main function to run all examples
|
|
473
|
+
*/
|
|
474
|
+
async function main() {
|
|
475
|
+
console.log('='.repeat(80));
|
|
476
|
+
console.log('PERFORMANCE OPTIMIZATION PATTERNS');
|
|
477
|
+
console.log('='.repeat(80));
|
|
478
|
+
|
|
479
|
+
try {
|
|
480
|
+
await exampleDelimiterCaching();
|
|
481
|
+
await exampleMemoryEfficientStreaming();
|
|
482
|
+
await exampleParallelProcessing();
|
|
483
|
+
await exampleOptimizedFileIO();
|
|
484
|
+
|
|
485
|
+
console.log('\n' + '='.repeat(80));
|
|
486
|
+
console.log('ALL OPTIMIZATION EXAMPLES COMPLETED');
|
|
487
|
+
console.log('='.repeat(80));
|
|
488
|
+
} catch (error) {
|
|
489
|
+
console.error('\nError running examples:', error);
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Run examples if this file is executed directly
|
|
495
|
+
if (require.main === module) {
|
|
496
|
+
main();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
module.exports = {
|
|
500
|
+
exampleDelimiterCaching,
|
|
501
|
+
exampleMemoryEfficientStreaming,
|
|
502
|
+
exampleParallelProcessing,
|
|
503
|
+
exampleOptimizedFileIO
|
|
504
|
+
};
|