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
package/errors.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
/**
|
|
2
3
|
* Custom error classes for jtcsv
|
|
3
4
|
*/
|
|
@@ -53,11 +54,93 @@ class FileSystemError extends JtcsvError {
|
|
|
53
54
|
* Error for parsing/formatting issues
|
|
54
55
|
*/
|
|
55
56
|
class ParsingError extends JtcsvError {
|
|
56
|
-
constructor(message, lineNumber = null, column = null) {
|
|
57
|
-
|
|
57
|
+
constructor(message, lineNumber = null, column = null, context = null, expected = null, actual = null) {
|
|
58
|
+
// Build detailed message
|
|
59
|
+
let detailedMessage = message;
|
|
60
|
+
|
|
61
|
+
if (lineNumber !== null) {
|
|
62
|
+
detailedMessage += ` at line ${lineNumber}`;
|
|
63
|
+
if (column !== null) {
|
|
64
|
+
detailedMessage += `, column ${column}`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (context !== null) {
|
|
69
|
+
detailedMessage += `\nContext: ${context}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (expected !== null && actual !== null) {
|
|
73
|
+
detailedMessage += `\nExpected: ${expected}`;
|
|
74
|
+
detailedMessage += `\nActual: ${actual}`;
|
|
75
|
+
} else if (expected !== null) {
|
|
76
|
+
detailedMessage += `\nExpected: ${expected}`;
|
|
77
|
+
} else if (actual !== null) {
|
|
78
|
+
detailedMessage += `\nActual: ${actual}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
super(detailedMessage, 'PARSING_ERROR');
|
|
58
82
|
this.name = 'ParsingError';
|
|
59
83
|
this.lineNumber = lineNumber;
|
|
60
84
|
this.column = column;
|
|
85
|
+
this.context = context;
|
|
86
|
+
this.expected = expected;
|
|
87
|
+
this.actual = actual;
|
|
88
|
+
this.originalMessage = message;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Create a ParsingError for CSV format issues
|
|
93
|
+
*/
|
|
94
|
+
static csvFormat(message, lineNumber = null, column = null, rowContent = null) {
|
|
95
|
+
let context = null;
|
|
96
|
+
if (rowContent !== null) {
|
|
97
|
+
context = `Row content: "${rowContent.substring(0, 100)}${rowContent.length > 100 ? '...' : ''}"`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return new ParsingError(
|
|
101
|
+
`CSV format error: ${message}`,
|
|
102
|
+
lineNumber,
|
|
103
|
+
column,
|
|
104
|
+
context
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Create a ParsingError for field count mismatch
|
|
110
|
+
*/
|
|
111
|
+
static fieldCountMismatch(expectedCount, actualCount, lineNumber = null, rowContent = null) {
|
|
112
|
+
return new ParsingError(
|
|
113
|
+
'Field count mismatch',
|
|
114
|
+
lineNumber,
|
|
115
|
+
null,
|
|
116
|
+
rowContent ? `Row: "${rowContent.substring(0, 100)}${rowContent.length > 100 ? '...' : ''}"` : null,
|
|
117
|
+
`${expectedCount} fields`,
|
|
118
|
+
`${actualCount} fields`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Create a ParsingError for unclosed quotes
|
|
124
|
+
*/
|
|
125
|
+
static unclosedQuotes(lineNumber = null, column = null, content = null) {
|
|
126
|
+
return new ParsingError(
|
|
127
|
+
'Unclosed quotes in CSV',
|
|
128
|
+
lineNumber,
|
|
129
|
+
column,
|
|
130
|
+
content ? `Content: "${content}"` : null
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Create a ParsingError for invalid delimiter
|
|
136
|
+
*/
|
|
137
|
+
static invalidDelimiter(delimiter, lineNumber = null, context = null) {
|
|
138
|
+
return new ParsingError(
|
|
139
|
+
`Invalid delimiter '${delimiter}'`,
|
|
140
|
+
lineNumber,
|
|
141
|
+
null,
|
|
142
|
+
context
|
|
143
|
+
);
|
|
61
144
|
}
|
|
62
145
|
}
|
|
63
146
|
|
|
@@ -83,6 +166,108 @@ class ConfigurationError extends JtcsvError {
|
|
|
83
166
|
}
|
|
84
167
|
}
|
|
85
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Utility function to create detailed error messages
|
|
171
|
+
*/
|
|
172
|
+
function createDetailedErrorMessage(baseMessage, details = {}) {
|
|
173
|
+
let message = baseMessage;
|
|
174
|
+
|
|
175
|
+
if (details.lineNumber !== undefined) {
|
|
176
|
+
message += ` at line ${details.lineNumber}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (details.column !== undefined) {
|
|
180
|
+
message += `, column ${details.column}`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (details.context !== undefined) {
|
|
184
|
+
message += `\nContext: ${details.context}`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (details.expected !== undefined) {
|
|
188
|
+
message += `\nExpected: ${details.expected}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (details.actual !== undefined) {
|
|
192
|
+
message += `\nActual: ${details.actual}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (details.suggestion !== undefined) {
|
|
196
|
+
message += `\nSuggestion: ${details.suggestion}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (details.codeSnippet !== undefined) {
|
|
200
|
+
message += `\nCode snippet: ${details.codeSnippet}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return message;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Error context builder for better debugging
|
|
208
|
+
*/
|
|
209
|
+
class ErrorContext {
|
|
210
|
+
constructor() {
|
|
211
|
+
this.details = {};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
lineNumber(line) {
|
|
215
|
+
this.details.lineNumber = line;
|
|
216
|
+
return this;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
column(col) {
|
|
220
|
+
this.details.column = col;
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
context(ctx) {
|
|
225
|
+
this.details.context = ctx;
|
|
226
|
+
return this;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
expected(exp) {
|
|
230
|
+
this.details.expected = exp;
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
actual(act) {
|
|
235
|
+
this.details.actual = act;
|
|
236
|
+
return this;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
suggestion(sugg) {
|
|
240
|
+
this.details.suggestion = sugg;
|
|
241
|
+
return this;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
codeSnippet(snippet) {
|
|
245
|
+
this.details.codeSnippet = snippet;
|
|
246
|
+
return this;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
buildMessage(baseMessage) {
|
|
250
|
+
return createDetailedErrorMessage(baseMessage, this.details);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
throwParsingError(baseMessage) {
|
|
254
|
+
const message = this.buildMessage(baseMessage);
|
|
255
|
+
throw new ParsingError(
|
|
256
|
+
message,
|
|
257
|
+
this.details.lineNumber,
|
|
258
|
+
this.details.column,
|
|
259
|
+
this.details.context,
|
|
260
|
+
this.details.expected,
|
|
261
|
+
this.details.actual
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
throwValidationError(baseMessage) {
|
|
266
|
+
const message = this.buildMessage(baseMessage);
|
|
267
|
+
throw new ValidationError(message);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
86
271
|
const ERROR_CODES = {
|
|
87
272
|
JTCSV_ERROR: 'JTCSV_ERROR',
|
|
88
273
|
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
@@ -201,6 +386,7 @@ module.exports = {
|
|
|
201
386
|
ConfigurationError,
|
|
202
387
|
ERROR_CODES,
|
|
203
388
|
createErrorMessage,
|
|
389
|
+
ErrorContext,
|
|
204
390
|
handleError,
|
|
205
391
|
safeExecute,
|
|
206
392
|
safeExecuteAsync,
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Example: Conditional Field Transformations
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates how to use jtcsv for complex data transformations
|
|
5
|
+
* with conditional logic, field validation, and custom formatting.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { csvToJson, jsonToCsv, createCsvToJsonStream } = require('../../index.js');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const { pipeline } = require('stream/promises');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Example 1: Conditional Transformation with Business Logic
|
|
14
|
+
*
|
|
15
|
+
* Scenario: Process sales data with different tax rates based on region
|
|
16
|
+
* and apply discounts for bulk purchases.
|
|
17
|
+
*/
|
|
18
|
+
async function exampleConditionalTransformation() {
|
|
19
|
+
console.log('=== Example 1: Conditional Transformation ===\n');
|
|
20
|
+
|
|
21
|
+
const salesCsv = `order_id,customer_id,region,product,quantity,unit_price,tax_rate
|
|
22
|
+
ORD001,CUST001,US,Widget A,10,25.99,0.08
|
|
23
|
+
ORD002,CUST002,EU,Widget B,5,49.99,0.21
|
|
24
|
+
ORD003,CUST003,US,Widget C,100,12.50,0.08
|
|
25
|
+
ORD004,CUST004,ASIA,Widget D,25,89.99,0.10
|
|
26
|
+
ORD005,CUST005,EU,Widget E,2,199.99,0.21`;
|
|
27
|
+
|
|
28
|
+
const transformedData = csvToJson(salesCsv, {
|
|
29
|
+
hasHeaders: true,
|
|
30
|
+
parseNumbers: true,
|
|
31
|
+
transform: (row) => {
|
|
32
|
+
// Calculate subtotal
|
|
33
|
+
const subtotal = row.quantity * row.unit_price;
|
|
34
|
+
|
|
35
|
+
// Apply bulk discount (10% for 50+ units)
|
|
36
|
+
let discountRate = 0;
|
|
37
|
+
if (row.quantity >= 50) {
|
|
38
|
+
discountRate = 0.10;
|
|
39
|
+
} else if (row.quantity >= 20) {
|
|
40
|
+
discountRate = 0.05;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const discountAmount = subtotal * discountRate;
|
|
44
|
+
const discountedSubtotal = subtotal - discountAmount;
|
|
45
|
+
|
|
46
|
+
// Calculate tax based on region
|
|
47
|
+
let taxAmount = discountedSubtotal * row.tax_rate;
|
|
48
|
+
|
|
49
|
+
// Special tax rules for specific regions
|
|
50
|
+
if (row.region === 'EU' && discountedSubtotal > 1000) {
|
|
51
|
+
taxAmount *= 0.9; // 10% tax reduction for large EU orders
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const total = discountedSubtotal + taxAmount;
|
|
55
|
+
|
|
56
|
+
// Add derived fields
|
|
57
|
+
return {
|
|
58
|
+
...row,
|
|
59
|
+
subtotal: Math.round(subtotal * 100) / 100,
|
|
60
|
+
discount_rate: discountRate,
|
|
61
|
+
discount_amount: Math.round(discountAmount * 100) / 100,
|
|
62
|
+
discounted_subtotal: Math.round(discountedSubtotal * 100) / 100,
|
|
63
|
+
tax_amount: Math.round(taxAmount * 100) / 100,
|
|
64
|
+
total: Math.round(total * 100) / 100,
|
|
65
|
+
currency: row.region === 'EU' ? 'EUR' : 'USD',
|
|
66
|
+
processed_at: new Date().toISOString(),
|
|
67
|
+
|
|
68
|
+
// Add business logic flags
|
|
69
|
+
is_bulk_order: row.quantity >= 20,
|
|
70
|
+
requires_vat_certificate: row.region === 'EU' && total > 1000,
|
|
71
|
+
is_export: row.region !== 'US'
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
console.log('Transformed Sales Data:');
|
|
77
|
+
transformedData.forEach((order, index) => {
|
|
78
|
+
console.log(`\nOrder ${index + 1}:`);
|
|
79
|
+
console.log(` ID: ${order.order_id}, Region: ${order.region}`);
|
|
80
|
+
console.log(` Product: ${order.product}, Quantity: ${order.quantity}`);
|
|
81
|
+
console.log(` Subtotal: ${order.currency} ${order.subtotal}`);
|
|
82
|
+
console.log(` Discount: ${order.currency} ${order.discount_amount} (${order.discount_rate * 100}%)`);
|
|
83
|
+
console.log(` Tax: ${order.currency} ${order.tax_amount}`);
|
|
84
|
+
console.log(` Total: ${order.currency} ${order.total}`);
|
|
85
|
+
console.log(` Flags: Bulk: ${order.is_bulk_order}, VAT Cert: ${order.requires_vat_certificate}, Export: ${order.is_export}`);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Convert back to CSV with selected columns
|
|
89
|
+
const outputCsv = jsonToCsv(transformedData, {
|
|
90
|
+
template: {
|
|
91
|
+
order_id: '',
|
|
92
|
+
region: '',
|
|
93
|
+
product: '',
|
|
94
|
+
quantity: '',
|
|
95
|
+
subtotal: '',
|
|
96
|
+
discount_amount: '',
|
|
97
|
+
tax_amount: '',
|
|
98
|
+
total: '',
|
|
99
|
+
currency: '',
|
|
100
|
+
is_bulk_order: '',
|
|
101
|
+
requires_vat_certificate: ''
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
console.log('\nGenerated CSV for accounting system:');
|
|
106
|
+
console.log(outputCsv);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Example 2: Streaming with Real-time Validation
|
|
111
|
+
*
|
|
112
|
+
* Scenario: Process large log files with validation and error handling
|
|
113
|
+
*/
|
|
114
|
+
async function exampleStreamingValidation() {
|
|
115
|
+
console.log('\n\n=== Example 2: Streaming with Validation ===\n');
|
|
116
|
+
|
|
117
|
+
// Simulate log data
|
|
118
|
+
const logData = `timestamp,user_id,action,resource,status_code,response_time
|
|
119
|
+
2024-01-15T10:30:00Z,user123,GET,/api/users,200,145
|
|
120
|
+
2024-01-15T10:31:00Z,user456,POST,/api/orders,201,230
|
|
121
|
+
2024-01-15T10:32:00Z,user789,GET,/api/products,404,89
|
|
122
|
+
2024-01-15T10:33:00Z,user123,DELETE,/api/users/456,403,312
|
|
123
|
+
2024-01-15T10:34:00Z,user999,GET,/api/health,200,45
|
|
124
|
+
2024-01-15T10:35:00Z,user123,PUT,/api/orders/789,200,567`;
|
|
125
|
+
|
|
126
|
+
const errorLog = [];
|
|
127
|
+
const stats = {
|
|
128
|
+
total: 0,
|
|
129
|
+
success: 0,
|
|
130
|
+
errors: 0,
|
|
131
|
+
slowRequests: 0
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const transformStream = createCsvToJsonStream({
|
|
135
|
+
hasHeaders: true,
|
|
136
|
+
parseNumbers: true,
|
|
137
|
+
onError: (error, row, rowNumber) => {
|
|
138
|
+
stats.errors++;
|
|
139
|
+
errorLog.push({
|
|
140
|
+
rowNumber,
|
|
141
|
+
row,
|
|
142
|
+
error: error.message,
|
|
143
|
+
timestamp: new Date().toISOString()
|
|
144
|
+
});
|
|
145
|
+
return null; // Skip this row
|
|
146
|
+
},
|
|
147
|
+
validate: (row) => {
|
|
148
|
+
// Validate required fields
|
|
149
|
+
if (!row.timestamp || !row.user_id || !row.action) {
|
|
150
|
+
throw new Error('Missing required fields');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Validate timestamp format
|
|
154
|
+
if (isNaN(new Date(row.timestamp).getTime())) {
|
|
155
|
+
throw new Error('Invalid timestamp format');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Validate status code
|
|
159
|
+
if (row.status_code < 100 || row.status_code > 599) {
|
|
160
|
+
throw new Error('Invalid HTTP status code');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Validate response time
|
|
164
|
+
if (row.response_time < 0 || row.response_time > 10000) {
|
|
165
|
+
throw new Error('Invalid response time');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return true;
|
|
169
|
+
},
|
|
170
|
+
transform: (row) => {
|
|
171
|
+
stats.total++;
|
|
172
|
+
|
|
173
|
+
// Add derived fields
|
|
174
|
+
const processedRow = {
|
|
175
|
+
...row,
|
|
176
|
+
is_success: row.status_code >= 200 && row.status_code < 300,
|
|
177
|
+
is_error: row.status_code >= 400,
|
|
178
|
+
is_slow: row.response_time > 500,
|
|
179
|
+
processing_timestamp: new Date().toISOString()
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
if (processedRow.is_success) {
|
|
183
|
+
stats.success++;
|
|
184
|
+
}
|
|
185
|
+
if (processedRow.is_slow) {
|
|
186
|
+
stats.slowRequests++;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return processedRow;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Process the data
|
|
194
|
+
const { Readable, Writable } = require('stream');
|
|
195
|
+
const readable = Readable.from([logData]);
|
|
196
|
+
const results = [];
|
|
197
|
+
|
|
198
|
+
const collector = new Writable({
|
|
199
|
+
objectMode: true,
|
|
200
|
+
write(chunk, encoding, callback) {
|
|
201
|
+
results.push(chunk);
|
|
202
|
+
callback();
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
await pipeline(readable, transformStream, collector);
|
|
207
|
+
|
|
208
|
+
console.log('Processing Statistics:');
|
|
209
|
+
console.log(` Total rows: ${stats.total}`);
|
|
210
|
+
console.log(` Successful: ${stats.success}`);
|
|
211
|
+
console.log(` Errors: ${stats.errors}`);
|
|
212
|
+
console.log(` Slow requests (>500ms): ${stats.slowRequests}`);
|
|
213
|
+
|
|
214
|
+
if (errorLog.length > 0) {
|
|
215
|
+
console.log('\nError Log:');
|
|
216
|
+
errorLog.forEach(error => {
|
|
217
|
+
console.log(` Row ${error.rowNumber}: ${error.error}`);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
console.log('\nSample Processed Rows:');
|
|
222
|
+
results.slice(0, 3).forEach((row, index) => {
|
|
223
|
+
console.log(` ${index + 1}. ${row.user_id} - ${row.action} ${row.resource} - ${row.status_code} (${row.response_time}ms)`);
|
|
224
|
+
console.log(` Success: ${row.is_success}, Error: ${row.is_error}, Slow: ${row.is_slow}`);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Example 3: Database Export/Import Workflow
|
|
230
|
+
*
|
|
231
|
+
* Scenario: Export data from database, transform, and import to another system
|
|
232
|
+
*/
|
|
233
|
+
async function exampleDatabaseWorkflow() {
|
|
234
|
+
console.log('\n\n=== Example 3: Database Export/Import Workflow ===\n');
|
|
235
|
+
|
|
236
|
+
// Simulate database export (in real scenario, this would come from PostgreSQL/MongoDB)
|
|
237
|
+
const databaseData = [
|
|
238
|
+
{
|
|
239
|
+
id: 1,
|
|
240
|
+
name: 'John Doe',
|
|
241
|
+
email: 'john@example.com',
|
|
242
|
+
department: 'Engineering',
|
|
243
|
+
salary: 85000,
|
|
244
|
+
hire_date: '2020-03-15',
|
|
245
|
+
active: true
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
id: 2,
|
|
249
|
+
name: 'Jane Smith',
|
|
250
|
+
email: 'jane@example.com',
|
|
251
|
+
department: 'Marketing',
|
|
252
|
+
salary: 72000,
|
|
253
|
+
hire_date: '2021-07-22',
|
|
254
|
+
active: true
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: 3,
|
|
258
|
+
name: 'Bob Johnson',
|
|
259
|
+
email: 'bob@example.com',
|
|
260
|
+
department: 'Sales',
|
|
261
|
+
salary: 68000,
|
|
262
|
+
hire_date: '2019-11-30',
|
|
263
|
+
active: false
|
|
264
|
+
}
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
// Step 1: Export to CSV for HR system
|
|
268
|
+
console.log('Step 1: Exporting to HR System CSV');
|
|
269
|
+
const hrCsv = jsonToCsv(databaseData, {
|
|
270
|
+
renameMap: {
|
|
271
|
+
id: 'Employee_ID',
|
|
272
|
+
name: 'Full_Name',
|
|
273
|
+
email: 'Email_Address',
|
|
274
|
+
department: 'Department',
|
|
275
|
+
salary: 'Annual_Salary',
|
|
276
|
+
hire_date: 'Hire_Date',
|
|
277
|
+
active: 'Employment_Status'
|
|
278
|
+
},
|
|
279
|
+
transform: (row) => ({
|
|
280
|
+
...row,
|
|
281
|
+
Employment_Status: row.active ? 'ACTIVE' : 'INACTIVE',
|
|
282
|
+
Annual_Salary: `$${row.salary.toLocaleString()}`,
|
|
283
|
+
Export_Date: new Date().toISOString().split('T')[0]
|
|
284
|
+
})
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
console.log('HR System CSV:');
|
|
288
|
+
console.log(hrCsv);
|
|
289
|
+
|
|
290
|
+
// Step 2: Transform for payroll system (different format)
|
|
291
|
+
console.log('\nStep 2: Transforming for Payroll System');
|
|
292
|
+
const payrollData = databaseData
|
|
293
|
+
.filter(employee => employee.active)
|
|
294
|
+
.map(employee => ({
|
|
295
|
+
employee_id: `EMP${employee.id.toString().padStart(4, '0')}`,
|
|
296
|
+
employee_name: employee.name.toUpperCase(),
|
|
297
|
+
department_code: employee.department.substring(0, 3).toUpperCase(),
|
|
298
|
+
monthly_salary: Math.round(employee.salary / 12),
|
|
299
|
+
tax_id: `TAX${employee.id.toString().padStart(6, '0')}`,
|
|
300
|
+
payment_method: 'DIRECT_DEPOSIT',
|
|
301
|
+
bank_account: `****${employee.id.toString().padStart(4, '0')}`
|
|
302
|
+
}));
|
|
303
|
+
|
|
304
|
+
const payrollCsv = jsonToCsv(payrollData, {
|
|
305
|
+
delimiter: '|', // Payroll system uses pipe delimiter
|
|
306
|
+
rfc4180Compliant: true
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
console.log('Payroll System CSV (pipe-delimited):');
|
|
310
|
+
console.log(payrollCsv);
|
|
311
|
+
|
|
312
|
+
// Step 3: Import transformed data (simulate reading CSV back)
|
|
313
|
+
console.log('\nStep 3: Importing Transformed Data');
|
|
314
|
+
const importedData = csvToJson(payrollCsv, {
|
|
315
|
+
delimiter: '|',
|
|
316
|
+
hasHeaders: true,
|
|
317
|
+
parseNumbers: true
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
console.log('Imported Payroll Data:');
|
|
321
|
+
importedData.forEach(employee => {
|
|
322
|
+
console.log(` ${employee.employee_id}: ${employee.employee_name} - ${employee.department_code} - $${employee.monthly_salary}/month`);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Example 4: API Stream Handling with Error Recovery
|
|
328
|
+
*
|
|
329
|
+
* Scenario: Process streaming API responses with retry logic
|
|
330
|
+
*/
|
|
331
|
+
async function exampleApiStreamHandling() {
|
|
332
|
+
console.log('\n\n=== Example 4: API Stream Handling ===\n');
|
|
333
|
+
|
|
334
|
+
// Simulate API response stream
|
|
335
|
+
const apiResponses = [
|
|
336
|
+
'id,name,age,city\n1,Alice,30,New York\n2,Bob,25,Los Angeles\n3,Charlie,35,Chicago',
|
|
337
|
+
'id,name,age,city\n4,Diana,28,Miami\n5,Eve,32,Seattle\n6,Frank,40,Boston',
|
|
338
|
+
'id,name,age,city\n7,Grace,29,Denver\n8,Henry,31,Atlanta\n9,Ivy,27,Portland'
|
|
339
|
+
];
|
|
340
|
+
|
|
341
|
+
const processedChunks = [];
|
|
342
|
+
let chunkCount = 0;
|
|
343
|
+
|
|
344
|
+
// Simulate processing each API response chunk
|
|
345
|
+
for (const chunk of apiResponses) {
|
|
346
|
+
chunkCount++;
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
console.log(`Processing API chunk ${chunkCount}...`);
|
|
350
|
+
|
|
351
|
+
const data = csvToJson(chunk, {
|
|
352
|
+
hasHeaders: true,
|
|
353
|
+
parseNumbers: true,
|
|
354
|
+
onError: (error, row, rowNumber) => {
|
|
355
|
+
console.warn(` Warning in chunk ${chunkCount}, row ${rowNumber}: ${error.message}`);
|
|
356
|
+
// In real scenario, you might log to monitoring system
|
|
357
|
+
return null; // Skip problematic row
|
|
358
|
+
},
|
|
359
|
+
transform: (row) => {
|
|
360
|
+
// Add metadata
|
|
361
|
+
return {
|
|
362
|
+
...row,
|
|
363
|
+
processed_chunk: chunkCount,
|
|
364
|
+
processing_timestamp: new Date().toISOString(),
|
|
365
|
+
data_source: 'api_stream'
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
processedChunks.push(...data);
|
|
371
|
+
console.log(` Successfully processed ${data.length} rows`);
|
|
372
|
+
|
|
373
|
+
} catch (error) {
|
|
374
|
+
console.error(` Error processing chunk ${chunkCount}: ${error.message}`);
|
|
375
|
+
// In real scenario, implement retry logic here
|
|
376
|
+
console.log(' Implementing retry logic...');
|
|
377
|
+
|
|
378
|
+
// Simulate retry
|
|
379
|
+
try {
|
|
380
|
+
const retryData = csvToJson(chunk, {
|
|
381
|
+
hasHeaders: true,
|
|
382
|
+
parseNumbers: false, // Try without number parsing
|
|
383
|
+
transform: (row) => ({
|
|
384
|
+
...row,
|
|
385
|
+
age: parseInt(row.age, 10) || 0, // Manual parsing
|
|
386
|
+
processed_chunk: chunkCount,
|
|
387
|
+
processing_timestamp: new Date().toISOString(),
|
|
388
|
+
data_source: 'api_stream_retry',
|
|
389
|
+
had_error: true
|
|
390
|
+
})
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
processedChunks.push(...retryData);
|
|
394
|
+
console.log(` Retry successful: processed ${retryData.length} rows`);
|
|
395
|
+
} catch (retryError) {
|
|
396
|
+
console.error(` Retry failed: ${retryError.message}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
console.log('\nAPI Stream Processing Summary:');
|
|
402
|
+
console.log(` Total chunks: ${chunkCount}`);
|
|
403
|
+
console.log(` Total rows processed: ${processedChunks.length}`);
|
|
404
|
+
|
|
405
|
+
// Aggregate data
|
|
406
|
+
const averageAge = processedChunks.reduce((sum, row) => sum + row.age, 0) / processedChunks.length;
|
|
407
|
+
const cities = [...new Set(processedChunks.map(row => row.city))];
|
|
408
|
+
|
|
409
|
+
console.log(` Average age: ${averageAge.toFixed(1)}`);
|
|
410
|
+
console.log(` Unique cities: ${cities.join(', ')}`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Main function to run all examples
|
|
415
|
+
*/
|
|
416
|
+
async function main() {
|
|
417
|
+
console.log('='.repeat(80));
|
|
418
|
+
console.log('ADVANCED JTCSV EXAMPLES');
|
|
419
|
+
console.log('='.repeat(80));
|
|
420
|
+
|
|
421
|
+
try {
|
|
422
|
+
await exampleConditionalTransformation();
|
|
423
|
+
await exampleStreamingValidation();
|
|
424
|
+
await exampleDatabaseWorkflow();
|
|
425
|
+
await exampleApiStreamHandling();
|
|
426
|
+
|
|
427
|
+
console.log('\n' + '='.repeat(80));
|
|
428
|
+
console.log('ALL EXAMPLES COMPLETED SUCCESSFULLY');
|
|
429
|
+
console.log('='.repeat(80));
|
|
430
|
+
} catch (error) {
|
|
431
|
+
console.error('\nError running examples:', error);
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Run examples if this file is executed directly
|
|
437
|
+
if (require.main === module) {
|
|
438
|
+
main();
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
module.exports = {
|
|
442
|
+
exampleConditionalTransformation,
|
|
443
|
+
exampleStreamingValidation,
|
|
444
|
+
exampleDatabaseWorkflow,
|
|
445
|
+
exampleApiStreamHandling
|
|
446
|
+
};
|