jtcsv 1.2.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +252 -337
  2. package/bin/jtcsv.js +167 -85
  3. package/cli-tui.js +0 -0
  4. package/dist/jtcsv.cjs.js +1619 -0
  5. package/dist/jtcsv.cjs.js.map +1 -0
  6. package/dist/jtcsv.esm.js +1599 -0
  7. package/dist/jtcsv.esm.js.map +1 -0
  8. package/dist/jtcsv.umd.js +1625 -0
  9. package/dist/jtcsv.umd.js.map +1 -0
  10. package/examples/cli-tool.js +186 -0
  11. package/examples/express-api.js +167 -0
  12. package/examples/large-dataset-example.js +185 -0
  13. package/examples/plugin-excel-exporter.js +407 -0
  14. package/examples/simple-usage.js +280 -0
  15. package/examples/streaming-example.js +419 -0
  16. package/index.d.ts +4 -0
  17. package/json-save.js +1 -1
  18. package/package.json +128 -14
  19. package/plugins/README.md +373 -0
  20. package/plugins/express-middleware/README.md +306 -0
  21. package/plugins/express-middleware/example.js +136 -0
  22. package/plugins/express-middleware/index.d.ts +114 -0
  23. package/plugins/express-middleware/index.js +360 -0
  24. package/plugins/express-middleware/package.json +52 -0
  25. package/plugins/fastify-plugin/index.js +406 -0
  26. package/plugins/fastify-plugin/package.json +55 -0
  27. package/plugins/nextjs-api/README.md +452 -0
  28. package/plugins/nextjs-api/examples/ConverterComponent.jsx +386 -0
  29. package/plugins/nextjs-api/examples/api-convert.js +69 -0
  30. package/plugins/nextjs-api/index.js +388 -0
  31. package/plugins/nextjs-api/package.json +63 -0
  32. package/plugins/nextjs-api/route.js +372 -0
  33. package/src/browser/browser-functions.js +189 -0
  34. package/src/browser/csv-to-json-browser.js +442 -0
  35. package/src/browser/errors-browser.js +194 -0
  36. package/src/browser/index.js +79 -0
  37. package/src/browser/json-to-csv-browser.js +309 -0
  38. package/src/browser/workers/csv-parser.worker.js +359 -0
  39. package/src/browser/workers/worker-pool.js +467 -0
  40. package/src/core/plugin-system.js +472 -0
  41. package/src/engines/fast-path-engine-new.js +338 -0
  42. package/src/engines/fast-path-engine.js +347 -0
  43. package/src/formats/ndjson-parser.js +419 -0
  44. package/src/index-with-plugins.js +349 -0
  45. package/stream-csv-to-json.js +1 -1
  46. package/stream-json-to-csv.js +1 -1
package/bin/jtcsv.js CHANGED
@@ -41,8 +41,8 @@ ${color('USAGE:', 'bright')}
41
41
  jtcsv [command] [options] [file...]
42
42
 
43
43
  ${color('COMMANDS:', 'bright')}
44
- ${color('json2csv', 'green')} Convert JSON to CSV
45
- ${color('csv2json', 'green')} Convert CSV to JSON
44
+ ${color('json-to-csv', 'green')} Convert JSON to CSV (alias: json2csv)
45
+ ${color('csv-to-json', 'green')} Convert CSV to JSON (alias: csv2json)
46
46
  ${color('stream', 'yellow')} Streaming conversion for large files
47
47
  ${color('batch', 'yellow')} Batch process multiple files
48
48
  ${color('tui', 'magenta')} Launch Terminal User Interface (requires blessed)
@@ -51,23 +51,29 @@ ${color('COMMANDS:', 'bright')}
51
51
 
52
52
  ${color('EXAMPLES:', 'bright')}
53
53
  ${color('Convert JSON file to CSV:', 'dim')}
54
- jtcsv json2csv input.json output.csv --delimiter=,
54
+ jtcsv json-to-csv input.json output.csv --delimiter=,
55
55
 
56
56
  ${color('Convert CSV file to JSON:', 'dim')}
57
- jtcsv csv2json input.csv output.json --parse-numbers
57
+ jtcsv csv-to-json input.csv output.json --parse-numbers --auto-detect
58
58
 
59
59
  ${color('Stream large JSON file to CSV:', 'dim')}
60
- jtcsv stream json2csv large.json output.csv --max-records=1000000
60
+ jtcsv stream json-to-csv large.json output.csv --max-records=1000000
61
61
 
62
62
  ${color('Launch TUI interface:', 'dim')}
63
63
  jtcsv tui
64
64
 
65
65
  ${color('OPTIONS:', 'bright')}
66
66
  ${color('--delimiter=', 'cyan')}CHAR CSV delimiter (default: ;)
67
+ ${color('--auto-detect', 'cyan')} Auto-detect delimiter (default: true)
68
+ ${color('--candidates=', 'cyan')}LIST Delimiter candidates (default: ;,\t|)
67
69
  ${color('--no-headers', 'cyan')} Exclude headers from CSV output
68
70
  ${color('--parse-numbers', 'cyan')} Parse numeric values in CSV
69
71
  ${color('--parse-booleans', 'cyan')} Parse boolean values in CSV
72
+ ${color('--no-trim', 'cyan')} Don't trim whitespace from CSV values
73
+ ${color('--rename=', 'cyan')}JSON Rename columns (JSON map)
74
+ ${color('--template=', 'cyan')}JSON Column order template (JSON object)
70
75
  ${color('--no-injection-protection', 'cyan')} Disable CSV injection protection
76
+ ${color('--no-rfc4180', 'cyan')} Disable RFC 4180 compliance
71
77
  ${color('--max-records=', 'cyan')}N Maximum records to process (optional, no limit by default)
72
78
  ${color('--max-rows=', 'cyan')}N Maximum rows to process (optional, no limit by default)
73
79
  ${color('--pretty', 'cyan')} Pretty print JSON output
@@ -109,8 +115,19 @@ async function convertJsonToCsv(inputFile, outputFile, options) {
109
115
 
110
116
  console.log(color(`Converting ${jsonData.length} records...`, 'dim'));
111
117
 
118
+ // Prepare options for jtcsv
119
+ const jtcsvOptions = {
120
+ delimiter: options.delimiter,
121
+ includeHeaders: options.includeHeaders,
122
+ renameMap: options.renameMap,
123
+ template: options.template,
124
+ maxRecords: options.maxRecords,
125
+ preventCsvInjection: options.preventCsvInjection,
126
+ rfc4180Compliant: options.rfc4180Compliant
127
+ };
128
+
112
129
  // Convert to CSV
113
- const csvData = jtcsv.jsonToCsv(jsonData, options);
130
+ const csvData = jtcsv.jsonToCsv(jsonData, jtcsvOptions);
114
131
 
115
132
  // Write output file
116
133
  await fs.promises.writeFile(outputFile, csvData, 'utf8');
@@ -129,10 +146,23 @@ async function convertCsvToJson(inputFile, outputFile, options) {
129
146
  const startTime = Date.now();
130
147
 
131
148
  try {
132
- console.log(color(`Reading CSV file...`, 'dim'));
149
+ console.log(color('Reading CSV file...', 'dim'));
150
+
151
+ // Prepare options for jtcsv
152
+ const jtcsvOptions = {
153
+ delimiter: options.delimiter,
154
+ autoDetect: options.autoDetect,
155
+ candidates: options.candidates,
156
+ hasHeaders: options.hasHeaders,
157
+ renameMap: options.renameMap,
158
+ trim: options.trim,
159
+ parseNumbers: options.parseNumbers,
160
+ parseBooleans: options.parseBooleans,
161
+ maxRows: options.maxRows
162
+ };
133
163
 
134
164
  // Read and convert CSV
135
- const jsonData = await jtcsv.readCsvAsJson(inputFile, options);
165
+ const jsonData = await jtcsv.readCsvAsJson(inputFile, jtcsvOptions);
136
166
 
137
167
  // Format JSON
138
168
  const jsonOutput = options.pretty
@@ -156,7 +186,7 @@ async function streamJsonToCsv(inputFile, outputFile, options) {
156
186
  const startTime = Date.now();
157
187
 
158
188
  try {
159
- console.log(color(`Streaming conversion started...`, 'dim'));
189
+ console.log(color('Streaming conversion started...', 'dim'));
160
190
 
161
191
  // Create streams
162
192
  const readStream = fs.createReadStream(inputFile, 'utf8');
@@ -235,10 +265,17 @@ async function launchTUI() {
235
265
  function parseOptions(args) {
236
266
  const options = {
237
267
  delimiter: ';',
268
+ autoDetect: true,
269
+ candidates: [';', ',', '\t', '|'],
270
+ hasHeaders: true,
238
271
  includeHeaders: true,
272
+ renameMap: undefined,
273
+ template: undefined,
274
+ trim: true,
239
275
  parseNumbers: false,
240
276
  parseBooleans: false,
241
277
  preventCsvInjection: true,
278
+ rfc4180Compliant: true,
242
279
  maxRecords: undefined,
243
280
  maxRows: undefined,
244
281
  pretty: false,
@@ -255,36 +292,72 @@ function parseOptions(args) {
255
292
  const [key, value] = arg.slice(2).split('=');
256
293
 
257
294
  switch (key) {
258
- case 'delimiter':
259
- options.delimiter = value || ',';
260
- break;
261
- case 'no-headers':
262
- options.includeHeaders = false;
263
- break;
264
- case 'parse-numbers':
265
- options.parseNumbers = true;
266
- break;
267
- case 'parse-booleans':
268
- options.parseBooleans = true;
269
- break;
270
- case 'no-injection-protection':
271
- options.preventCsvInjection = false;
272
- break;
273
- case 'max-records':
274
- options.maxRecords = parseInt(value, 10);
275
- break;
276
- case 'max-rows':
277
- options.maxRows = parseInt(value, 10);
278
- break;
279
- case 'pretty':
280
- options.pretty = true;
281
- break;
282
- case 'silent':
283
- options.silent = true;
284
- break;
285
- case 'verbose':
286
- options.verbose = true;
287
- break;
295
+ case 'delimiter':
296
+ options.delimiter = value || ',';
297
+ options.autoDetect = false; // Disable auto-detect if delimiter is specified
298
+ break;
299
+ case 'auto-detect':
300
+ options.autoDetect = value !== 'false';
301
+ break;
302
+ case 'candidates':
303
+ options.candidates = value ? value.split(',') : [';', ',', '\t', '|'];
304
+ break;
305
+ case 'no-headers':
306
+ options.includeHeaders = false;
307
+ options.hasHeaders = false;
308
+ break;
309
+ case 'parse-numbers':
310
+ options.parseNumbers = true;
311
+ break;
312
+ case 'parse-booleans':
313
+ options.parseBooleans = true;
314
+ break;
315
+ case 'no-trim':
316
+ options.trim = false;
317
+ break;
318
+ case 'rename':
319
+ try {
320
+ // Handle both quoted and unquoted JSON
321
+ const jsonStr = value || '{}';
322
+ // Remove surrounding single quotes if present
323
+ const cleanStr = jsonStr.replace(/^'|'$/g, '').replace(/^"|"$/g, '');
324
+ options.renameMap = JSON.parse(cleanStr);
325
+ } catch (e) {
326
+ throw new Error(`Invalid JSON in --rename option: ${e.message}. Value: ${value}`);
327
+ }
328
+ break;
329
+ case 'template':
330
+ try {
331
+ // Handle both quoted and unquoted JSON
332
+ const jsonStr = value || '{}';
333
+ // Remove surrounding single quotes if present
334
+ const cleanStr = jsonStr.replace(/^'|'$/g, '').replace(/^"|"$/g, '');
335
+ options.template = JSON.parse(cleanStr);
336
+ } catch (e) {
337
+ throw new Error(`Invalid JSON in --template option: ${e.message}. Value: ${value}`);
338
+ }
339
+ break;
340
+ case 'no-injection-protection':
341
+ options.preventCsvInjection = false;
342
+ break;
343
+ case 'no-rfc4180':
344
+ options.rfc4180Compliant = false;
345
+ break;
346
+ case 'max-records':
347
+ options.maxRecords = parseInt(value, 10);
348
+ break;
349
+ case 'max-rows':
350
+ options.maxRows = parseInt(value, 10);
351
+ break;
352
+ case 'pretty':
353
+ options.pretty = true;
354
+ break;
355
+ case 'silent':
356
+ options.silent = true;
357
+ break;
358
+ case 'verbose':
359
+ options.verbose = true;
360
+ break;
288
361
  }
289
362
  } else if (!arg.startsWith('-')) {
290
363
  files.push(arg);
@@ -312,57 +385,59 @@ async function main() {
312
385
  }
313
386
 
314
387
  switch (command) {
315
- case 'json2csv':
316
- if (files.length < 2) {
317
- console.error(color('Error: Input and output files required', 'red'));
318
- console.log(color('Usage: jtcsv json2csv input.json output.csv', 'cyan'));
319
- process.exit(1);
320
- }
321
- await convertJsonToCsv(files[0], files[1], options);
322
- break;
388
+ case 'json-to-csv':
389
+ case 'json2csv': // Backward compatibility
390
+ if (files.length < 2) {
391
+ console.error(color('Error: Input and output files required', 'red'));
392
+ console.log(color('Usage: jtcsv json-to-csv input.json output.csv', 'cyan'));
393
+ process.exit(1);
394
+ }
395
+ await convertJsonToCsv(files[0], files[1], options);
396
+ break;
323
397
 
324
- case 'csv2json':
325
- if (files.length < 2) {
326
- console.error(color('Error: Input and output files required', 'red'));
327
- console.log(color('Usage: jtcsv csv2json input.csv output.json', 'cyan'));
328
- process.exit(1);
329
- }
330
- await convertCsvToJson(files[0], files[1], options);
331
- break;
398
+ case 'csv-to-json':
399
+ case 'csv2json': // Backward compatibility
400
+ if (files.length < 2) {
401
+ console.error(color('Error: Input and output files required', 'red'));
402
+ console.log(color('Usage: jtcsv csv-to-json input.csv output.json', 'cyan'));
403
+ process.exit(1);
404
+ }
405
+ await convertCsvToJson(files[0], files[1], options);
406
+ break;
332
407
 
333
- case 'stream':
334
- if (args.length < 2) {
335
- console.error(color('Error: Streaming mode requires subcommand', 'red'));
336
- console.log(color('Usage: jtcsv stream [json2csv|csv2json] input output', 'cyan'));
337
- process.exit(1);
338
- }
339
- const streamCommand = args[1].toLowerCase();
340
- if (streamCommand === 'json2csv' && files.length >= 2) {
341
- await streamJsonToCsv(files[0], files[1], options);
342
- } else {
343
- console.error(color('Error: Invalid streaming command', 'red'));
344
- process.exit(1);
345
- }
346
- break;
408
+ case 'stream':
409
+ if (args.length < 2) {
410
+ console.error(color('Error: Streaming mode requires subcommand', 'red'));
411
+ console.log(color('Usage: jtcsv stream [json2csv|csv2json] input output', 'cyan'));
412
+ process.exit(1);
413
+ }
414
+ const streamCommand = args[1].toLowerCase();
415
+ if (streamCommand === 'json2csv' && files.length >= 2) {
416
+ await streamJsonToCsv(files[0], files[1], options);
417
+ } else {
418
+ console.error(color('Error: Invalid streaming command', 'red'));
419
+ process.exit(1);
420
+ }
421
+ break;
347
422
 
348
- case 'tui':
349
- await launchTUI();
350
- break;
423
+ case 'tui':
424
+ await launchTUI();
425
+ break;
351
426
 
352
- case 'help':
353
- showHelp();
354
- break;
427
+ case 'help':
428
+ showHelp();
429
+ break;
355
430
 
356
- case 'version':
357
- case '-v':
358
- case '--version':
359
- showVersion();
360
- break;
431
+ case 'version':
432
+ case '-v':
433
+ case '--version':
434
+ showVersion();
435
+ break;
361
436
 
362
- default:
363
- console.error(color(`Error: Unknown command '${command}'`, 'red'));
364
- console.log(color('Use jtcsv help for available commands', 'cyan'));
365
- process.exit(1);
437
+ default:
438
+ console.error(color(`Error: Unknown command '${command}'`, 'red'));
439
+ console.log(color('Use jtcsv help for available commands', 'cyan'));
440
+ process.exit(1);
366
441
  }
367
442
  }
368
443
 
@@ -391,4 +466,11 @@ if (require.main === module) {
391
466
  });
392
467
  }
393
468
 
394
- module.exports = { main };
469
+ module.exports = { main };
470
+
471
+
472
+
473
+
474
+
475
+
476
+
package/cli-tui.js CHANGED
Binary file