jtcsv 3.0.0 → 3.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 (258) hide show
  1. package/README.md +205 -146
  2. package/bin/jtcsv.ts +280 -202
  3. package/browser.d.ts +142 -0
  4. package/dist/benchmark.js +446 -0
  5. package/dist/benchmark.js.map +1 -0
  6. package/dist/bin/jtcsv.js +1940 -0
  7. package/dist/bin/jtcsv.js.map +1 -0
  8. package/dist/csv-to-json.js +1262 -0
  9. package/dist/csv-to-json.js.map +1 -0
  10. package/dist/errors.js +291 -0
  11. package/dist/errors.js.map +1 -0
  12. package/dist/eslint.config.js +147 -0
  13. package/dist/eslint.config.js.map +1 -0
  14. package/dist/index-core.js +95 -0
  15. package/dist/index-core.js.map +1 -0
  16. package/dist/index.js +93 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/json-save.js +229 -0
  19. package/dist/json-save.js.map +1 -0
  20. package/dist/json-to-csv.js +576 -0
  21. package/dist/json-to-csv.js.map +1 -0
  22. package/dist/jtcsv-core.cjs.js +336 -7
  23. package/dist/jtcsv-core.cjs.js.map +1 -1
  24. package/dist/jtcsv-core.esm.js +336 -7
  25. package/dist/jtcsv-core.esm.js.map +1 -1
  26. package/dist/jtcsv-core.umd.js +336 -7
  27. package/dist/jtcsv-core.umd.js.map +1 -1
  28. package/dist/jtcsv-full.cjs.js +336 -7
  29. package/dist/jtcsv-full.cjs.js.map +1 -1
  30. package/dist/jtcsv-full.esm.js +336 -7
  31. package/dist/jtcsv-full.esm.js.map +1 -1
  32. package/dist/jtcsv-full.umd.js +336 -7
  33. package/dist/jtcsv-full.umd.js.map +1 -1
  34. package/dist/jtcsv-workers.esm.js +9 -0
  35. package/dist/jtcsv-workers.esm.js.map +1 -1
  36. package/dist/jtcsv-workers.umd.js +9 -0
  37. package/dist/jtcsv-workers.umd.js.map +1 -1
  38. package/dist/jtcsv.cjs.js +1998 -2092
  39. package/dist/jtcsv.cjs.js.map +1 -1
  40. package/dist/jtcsv.esm.js +1994 -2092
  41. package/dist/jtcsv.esm.js.map +1 -1
  42. package/dist/jtcsv.umd.js +2157 -2251
  43. package/dist/jtcsv.umd.js.map +1 -1
  44. package/dist/plugins/express-middleware/index.js +350 -0
  45. package/dist/plugins/express-middleware/index.js.map +1 -0
  46. package/dist/plugins/fastify-plugin/index.js +315 -0
  47. package/dist/plugins/fastify-plugin/index.js.map +1 -0
  48. package/dist/plugins/hono/index.js +111 -0
  49. package/dist/plugins/hono/index.js.map +1 -0
  50. package/dist/plugins/nestjs/index.js +112 -0
  51. package/dist/plugins/nestjs/index.js.map +1 -0
  52. package/dist/plugins/nuxt/index.js +53 -0
  53. package/dist/plugins/nuxt/index.js.map +1 -0
  54. package/dist/plugins/remix/index.js +133 -0
  55. package/dist/plugins/remix/index.js.map +1 -0
  56. package/dist/plugins/sveltekit/index.js +155 -0
  57. package/dist/plugins/sveltekit/index.js.map +1 -0
  58. package/dist/plugins/trpc/index.js +136 -0
  59. package/dist/plugins/trpc/index.js.map +1 -0
  60. package/dist/run-demo.js +49 -0
  61. package/dist/run-demo.js.map +1 -0
  62. package/dist/src/browser/browser-functions.js +193 -0
  63. package/dist/src/browser/browser-functions.js.map +1 -0
  64. package/dist/src/browser/core.js +123 -0
  65. package/dist/src/browser/core.js.map +1 -0
  66. package/dist/src/browser/csv-to-json-browser.js +353 -0
  67. package/dist/src/browser/csv-to-json-browser.js.map +1 -0
  68. package/dist/src/browser/errors-browser.js +219 -0
  69. package/dist/src/browser/errors-browser.js.map +1 -0
  70. package/dist/src/browser/extensions/plugins.js +106 -0
  71. package/dist/src/browser/extensions/plugins.js.map +1 -0
  72. package/dist/src/browser/extensions/workers.js +66 -0
  73. package/dist/src/browser/extensions/workers.js.map +1 -0
  74. package/dist/src/browser/index.js +140 -0
  75. package/dist/src/browser/index.js.map +1 -0
  76. package/dist/src/browser/json-to-csv-browser.js +225 -0
  77. package/dist/src/browser/json-to-csv-browser.js.map +1 -0
  78. package/dist/src/browser/streams.js +340 -0
  79. package/dist/src/browser/streams.js.map +1 -0
  80. package/dist/src/browser/workers/csv-parser.worker.js +264 -0
  81. package/dist/src/browser/workers/csv-parser.worker.js.map +1 -0
  82. package/dist/src/browser/workers/worker-pool.js +338 -0
  83. package/dist/src/browser/workers/worker-pool.js.map +1 -0
  84. package/dist/src/core/delimiter-cache.js +196 -0
  85. package/dist/src/core/delimiter-cache.js.map +1 -0
  86. package/dist/src/core/node-optimizations.js +279 -0
  87. package/dist/src/core/node-optimizations.js.map +1 -0
  88. package/dist/src/core/plugin-system.js +399 -0
  89. package/dist/src/core/plugin-system.js.map +1 -0
  90. package/dist/src/core/transform-hooks.js +348 -0
  91. package/dist/src/core/transform-hooks.js.map +1 -0
  92. package/dist/src/engines/fast-path-engine-new.js +262 -0
  93. package/dist/src/engines/fast-path-engine-new.js.map +1 -0
  94. package/dist/src/engines/fast-path-engine.js +671 -0
  95. package/dist/src/engines/fast-path-engine.js.map +1 -0
  96. package/dist/src/errors.js +18 -0
  97. package/dist/src/errors.js.map +1 -0
  98. package/dist/src/formats/ndjson-parser.js +332 -0
  99. package/dist/src/formats/ndjson-parser.js.map +1 -0
  100. package/dist/src/formats/tsv-parser.js +230 -0
  101. package/dist/src/formats/tsv-parser.js.map +1 -0
  102. package/dist/src/index-with-plugins.js +259 -0
  103. package/dist/src/index-with-plugins.js.map +1 -0
  104. package/dist/src/types/index.js +3 -0
  105. package/dist/src/types/index.js.map +1 -0
  106. package/dist/src/utils/bom-utils.js +267 -0
  107. package/dist/src/utils/bom-utils.js.map +1 -0
  108. package/dist/src/utils/encoding-support.js +77 -0
  109. package/dist/src/utils/encoding-support.js.map +1 -0
  110. package/dist/src/utils/schema-validator.js +609 -0
  111. package/dist/src/utils/schema-validator.js.map +1 -0
  112. package/dist/src/utils/transform-loader.js +281 -0
  113. package/dist/src/utils/transform-loader.js.map +1 -0
  114. package/dist/src/utils/validators.js +40 -0
  115. package/dist/src/utils/validators.js.map +1 -0
  116. package/dist/src/utils/zod-adapter.js +144 -0
  117. package/dist/src/utils/zod-adapter.js.map +1 -0
  118. package/{src → dist/src}/web-server/index.js +251 -286
  119. package/dist/src/web-server/index.js.map +1 -0
  120. package/dist/src/workers/csv-multithreaded.js +211 -0
  121. package/dist/src/workers/csv-multithreaded.js.map +1 -0
  122. package/dist/src/workers/csv-parser.worker.js +179 -0
  123. package/dist/src/workers/csv-parser.worker.js.map +1 -0
  124. package/dist/src/workers/worker-pool.js +228 -0
  125. package/dist/src/workers/worker-pool.js.map +1 -0
  126. package/dist/stream-csv-to-json.js +665 -0
  127. package/dist/stream-csv-to-json.js.map +1 -0
  128. package/dist/stream-json-to-csv.js +389 -0
  129. package/dist/stream-json-to-csv.js.map +1 -0
  130. package/examples/advanced/conditional-transformations.ts +2 -2
  131. package/examples/advanced/performance-optimization.ts +2 -2
  132. package/examples/cli-advanced-usage.md +2 -0
  133. package/examples/cli-tool.ts +1 -1
  134. package/examples/large-dataset-example.ts +2 -2
  135. package/examples/simple-usage.ts +2 -2
  136. package/examples/streaming-example.ts +1 -1
  137. package/index.d.ts +186 -15
  138. package/package.json +43 -108
  139. package/plugins.d.ts +37 -0
  140. package/schema.d.ts +103 -0
  141. package/src/browser/csv-to-json-browser.ts +233 -3
  142. package/src/browser/errors-browser.ts +45 -28
  143. package/src/browser/json-to-csv-browser.ts +81 -5
  144. package/src/browser/streams.ts +73 -6
  145. package/src/core/delimiter-cache.ts +21 -11
  146. package/src/core/plugin-system.ts +343 -155
  147. package/src/core/transform-hooks.ts +20 -12
  148. package/src/engines/fast-path-engine.ts +48 -32
  149. package/src/errors.ts +1 -72
  150. package/src/formats/ndjson-parser.ts +6 -0
  151. package/src/formats/tsv-parser.ts +6 -0
  152. package/src/types/index.ts +21 -1
  153. package/src/utils/validators.ts +35 -0
  154. package/src/web-server/index.ts +1 -1
  155. package/bin/jtcsv.js +0 -2532
  156. package/csv-to-json.js +0 -711
  157. package/errors.js +0 -394
  158. package/examples/advanced/conditional-transformations.js +0 -446
  159. package/examples/advanced/csv-parser.worker.js +0 -89
  160. package/examples/advanced/nested-objects-example.js +0 -306
  161. package/examples/advanced/performance-optimization.js +0 -504
  162. package/examples/advanced/run-demo-server.js +0 -116
  163. package/examples/cli-batch-processing.js +0 -38
  164. package/examples/cli-tool.js +0 -183
  165. package/examples/error-handling.js +0 -338
  166. package/examples/express-api.js +0 -164
  167. package/examples/large-dataset-example.js +0 -182
  168. package/examples/ndjson-processing.js +0 -434
  169. package/examples/plugin-excel-exporter.js +0 -406
  170. package/examples/schema-validation.js +0 -640
  171. package/examples/simple-usage.js +0 -282
  172. package/examples/streaming-example.js +0 -418
  173. package/examples/web-workers-advanced.js +0 -28
  174. package/index.js +0 -82
  175. package/json-save.js +0 -255
  176. package/json-to-csv.js +0 -668
  177. package/plugins/README.md +0 -91
  178. package/plugins/express-middleware/README.md +0 -83
  179. package/plugins/express-middleware/example.js +0 -135
  180. package/plugins/express-middleware/example.ts +0 -135
  181. package/plugins/express-middleware/index.d.ts +0 -114
  182. package/plugins/express-middleware/index.js +0 -512
  183. package/plugins/express-middleware/index.ts +0 -557
  184. package/plugins/express-middleware/package.json +0 -52
  185. package/plugins/fastify-plugin/index.js +0 -404
  186. package/plugins/fastify-plugin/index.ts +0 -443
  187. package/plugins/fastify-plugin/package.json +0 -55
  188. package/plugins/hono/README.md +0 -28
  189. package/plugins/hono/index.d.ts +0 -12
  190. package/plugins/hono/index.js +0 -36
  191. package/plugins/hono/index.ts +0 -226
  192. package/plugins/hono/package.json +0 -35
  193. package/plugins/nestjs/README.md +0 -35
  194. package/plugins/nestjs/index.d.ts +0 -25
  195. package/plugins/nestjs/index.js +0 -77
  196. package/plugins/nestjs/index.ts +0 -201
  197. package/plugins/nestjs/package.json +0 -37
  198. package/plugins/nextjs-api/README.md +0 -57
  199. package/plugins/nextjs-api/examples/ConverterComponent.jsx +0 -386
  200. package/plugins/nextjs-api/examples/ConverterComponent.tsx +0 -386
  201. package/plugins/nextjs-api/examples/api-convert.js +0 -67
  202. package/plugins/nextjs-api/examples/api-convert.ts +0 -67
  203. package/plugins/nextjs-api/index.js +0 -387
  204. package/plugins/nextjs-api/index.tsx +0 -339
  205. package/plugins/nextjs-api/package.json +0 -63
  206. package/plugins/nextjs-api/route.js +0 -370
  207. package/plugins/nextjs-api/route.ts +0 -370
  208. package/plugins/nuxt/README.md +0 -24
  209. package/plugins/nuxt/index.js +0 -21
  210. package/plugins/nuxt/index.ts +0 -94
  211. package/plugins/nuxt/package.json +0 -35
  212. package/plugins/nuxt/runtime/composables/useJtcsv.js +0 -6
  213. package/plugins/nuxt/runtime/composables/useJtcsv.ts +0 -100
  214. package/plugins/nuxt/runtime/plugin.js +0 -6
  215. package/plugins/nuxt/runtime/plugin.ts +0 -71
  216. package/plugins/remix/README.md +0 -26
  217. package/plugins/remix/index.d.ts +0 -16
  218. package/plugins/remix/index.js +0 -62
  219. package/plugins/remix/index.ts +0 -260
  220. package/plugins/remix/package.json +0 -35
  221. package/plugins/sveltekit/README.md +0 -28
  222. package/plugins/sveltekit/index.d.ts +0 -17
  223. package/plugins/sveltekit/index.js +0 -54
  224. package/plugins/sveltekit/index.ts +0 -301
  225. package/plugins/sveltekit/package.json +0 -33
  226. package/plugins/trpc/README.md +0 -25
  227. package/plugins/trpc/index.d.ts +0 -7
  228. package/plugins/trpc/index.js +0 -32
  229. package/plugins/trpc/index.ts +0 -267
  230. package/plugins/trpc/package.json +0 -34
  231. package/src/browser/browser-functions.js +0 -219
  232. package/src/browser/core.js +0 -92
  233. package/src/browser/csv-to-json-browser.js +0 -722
  234. package/src/browser/errors-browser.js +0 -212
  235. package/src/browser/extensions/plugins.js +0 -92
  236. package/src/browser/extensions/workers.js +0 -39
  237. package/src/browser/index.js +0 -113
  238. package/src/browser/json-to-csv-browser.js +0 -319
  239. package/src/browser/streams.js +0 -403
  240. package/src/browser/workers/csv-parser.worker.js +0 -377
  241. package/src/browser/workers/worker-pool.js +0 -527
  242. package/src/core/delimiter-cache.js +0 -200
  243. package/src/core/node-optimizations.js +0 -408
  244. package/src/core/plugin-system.js +0 -494
  245. package/src/core/transform-hooks.js +0 -350
  246. package/src/engines/fast-path-engine-new.js +0 -338
  247. package/src/engines/fast-path-engine.js +0 -844
  248. package/src/errors.js +0 -26
  249. package/src/formats/ndjson-parser.js +0 -467
  250. package/src/formats/tsv-parser.js +0 -339
  251. package/src/index-with-plugins.js +0 -378
  252. package/src/utils/bom-utils.js +0 -259
  253. package/src/utils/encoding-support.js +0 -124
  254. package/src/utils/schema-validator.js +0 -594
  255. package/src/utils/transform-loader.js +0 -205
  256. package/src/utils/zod-adapter.js +0 -170
  257. package/stream-csv-to-json.js +0 -560
  258. package/stream-json-to-csv.js +0 -465
package/bin/jtcsv.ts CHANGED
@@ -11,7 +11,7 @@ import * as fs from 'fs';
11
11
  import * as path from 'path';
12
12
  import * as readline from 'readline';
13
13
  const { pipeline } = require('stream/promises');
14
- import * as jtcsv from '../index.js';
14
+ import * as jtcsv from '../index';
15
15
  const transformLoader = require('../src/utils/transform-loader');
16
16
  const schemaValidator = require('../src/utils/schema-validator');
17
17
 
@@ -88,7 +88,7 @@ function color(text, colorName: any): any {
88
88
  function showHelp(): void {
89
89
  console.log(`
90
90
  ${color('jtcsv CLI v' + VERSION, 'cyan')}
91
- ${color('The Complete JSONCSV Converter for Node.js', 'dim')}
91
+ ${color('The Complete JSON<->CSV Converter for Node.js', 'dim')}
92
92
 
93
93
  ${color('USAGE:', 'bright')}
94
94
  jtcsv [command] [options] [file...]
@@ -106,7 +106,7 @@ ${color('MAIN COMMANDS:', 'bright')}
106
106
  ${color('batch', 'yellow')} Batch process multiple files
107
107
  ${color('preprocess', 'magenta')} Preprocess JSON with deep unwrapping
108
108
  ${color('unwrap', 'magenta')} Flatten nested JSON structures (alias: flatten)
109
- ${color('tui', 'magenta')} Launch Terminal User Interface (@jtcsv/tui)
109
+ ${color('tui', 'magenta')} Launch Terminal User Interface (@jtcsv/tui)
110
110
  ${color('web', 'magenta')} Launch Web Interface (http://localhost:3000)
111
111
  ${color('help', 'blue')} Show this help message
112
112
  ${color('version', 'blue')} Show version information
@@ -147,7 +147,7 @@ ${color('EXAMPLES:', 'bright')}
147
147
  ${color('Batch convert JSON files:', 'dim')}
148
148
  jtcsv batch json-to-csv "data/*.json" "output/" --delimiter=;
149
149
 
150
- ${color('Launch TUI interface:', 'dim')}
150
+ ${color('Launch TUI interface:', 'dim')}
151
151
  jtcsv tui
152
152
 
153
153
  ${color('Launch Web interface:', 'dim')}
@@ -160,10 +160,12 @@ ${color('CONVERSION OPTIONS:', 'bright')}
160
160
  ${color('--no-headers', 'cyan')} Exclude headers from CSV output
161
161
  ${color('--parse-numbers', 'cyan')} Parse numeric values in CSV
162
162
  ${color('--parse-booleans', 'cyan')} Parse boolean values in CSV
163
- ${color('--no-trim', 'cyan')} Don't trim whitespace from CSV values
164
- ${color('--no-fast-path', 'cyan')} Disable fast-path parser (force quote-aware)
165
- ${color('--fast-path-mode=', 'cyan')}MODE Fast path output mode (objects|compact)
166
- ${color('--rename=', 'cyan')}JSON Rename columns (JSON map)
163
+ ${color('--no-trim', 'cyan')} Don't trim whitespace from CSV values
164
+ ${color('--no-fast-path', 'cyan')} Disable fast-path parser (force quote-aware)
165
+ ${color('--fast-path-mode=', 'cyan')}MODE Fast path output mode (objects|compact)
166
+ ${color('--repair-row-shifts', 'cyan')} Repair shifted rows with trailing empty fields
167
+ ${color('--normalize-quotes', 'cyan')} Normalize excessive quotes in parsed fields
168
+ ${color('--rename=', 'cyan')}JSON Rename columns (JSON map)
167
169
  ${color('--template=', 'cyan')}JSON Column order template (JSON object)
168
170
  ${color('--no-injection-protection', 'cyan')} Disable CSV injection protection
169
171
  ${color('--no-rfc4180', 'cyan')} Disable RFC 4180 compliance
@@ -196,17 +198,17 @@ ${color('CONVERSION OPTIONS:', 'bright')}
196
198
  ${color('--debug', 'cyan')} Show debug information
197
199
  ${color('--dry-run', 'cyan')} Show what would be done without actually doing it
198
200
  ${color('SECURITY FEATURES:', 'bright')}
199
- CSV injection protection (enabled by default)
200
- Path traversal protection
201
- Input validation and sanitization
202
- Size limits to prevent DoS attacks
203
- Schema validation support
201
+ - CSV injection protection (enabled by default)
202
+ - Path traversal protection
203
+ - Input validation and sanitization
204
+ - Size limits to prevent DoS attacks
205
+ - Schema validation support
204
206
 
205
207
  ${color('PERFORMANCE FEATURES:', 'bright')}
206
- Streaming for files >100MB
207
- Batch processing with parallel execution
208
- Memory-efficient preprocessing
209
- Configurable buffer sizes
208
+ - Streaming for files >100MB
209
+ - Batch processing with parallel execution
210
+ - Memory-efficient preprocessing
211
+ - Configurable buffer sizes
210
212
 
211
213
  ${color('LEARN MORE:', 'dim')}
212
214
  GitHub: https://github.com/Linol-Hamelton/jtcsv
@@ -215,57 +217,74 @@ ${color('CONVERSION OPTIONS:', 'bright')}
215
217
  `);
216
218
  }
217
219
 
218
- function showVersion(): void {
219
- console.log(`jtcsv v${VERSION}`);
220
- console.log(`Node.js ${process.version}`);
221
- console.log(`Platform: ${process.platform} ${process.arch}`);
222
- }
223
-
224
- // ============================================================================
225
- // CONVERSION FUNCTIONS
226
- // ============================================================================
220
+ function showVersion(): void {
221
+ console.log(`jtcsv v${VERSION}`);
222
+ console.log(`Node.js ${process.version}`);
223
+ console.log(`Platform: ${process.platform} ${process.arch}`);
224
+ }
225
+
226
+ async function readStdin(): Promise<string> {
227
+ return new Promise((resolve, reject) => {
228
+ let data = '';
229
+ process.stdin.setEncoding('utf8');
230
+ process.stdin.on('data', (chunk) => {
231
+ data += chunk;
232
+ });
233
+ process.stdin.on('end', () => resolve(data));
234
+ process.stdin.on('error', reject);
235
+ });
236
+ }
237
+
238
+ // ============================================================================
239
+ // CONVERSION FUNCTIONS
240
+ // ============================================================================
227
241
 
228
242
  async function convertJsonToCsv(inputFile, outputFile, options: any): Promise<ConversionResult> {
229
- const startTime = Date.now();
230
-
231
- try {
232
- // Read input file
233
- const inputData = await fs.promises.readFile(inputFile, 'utf8');
234
- const jsonData = JSON.parse(inputData);
235
-
236
- if (!Array.isArray(jsonData)) {
237
- throw new Error('JSON data must be an array of objects');
238
- }
239
-
240
- if (!options.silent) {
241
- console.log(
242
- color(
243
- `Converting ${jsonData.length.toLocaleString()} records...`,
244
- 'dim'
245
- )
246
- );
247
- }
248
-
249
- if (options.transform) {
250
- if (!options.silent) {
251
- console.log(
252
- color(`Applying transform from: ${options.transform}`, 'dim')
253
- );
254
- }
243
+ const startTime = Date.now();
244
+ const useStdin = inputFile === '-';
245
+ const useStdout = outputFile === '-';
246
+ const shouldLog = !options.silent && !useStdout;
247
+
248
+ try {
249
+ // Read input
250
+ const inputData = useStdin
251
+ ? await readStdin()
252
+ : await fs.promises.readFile(inputFile, 'utf8');
253
+ const jsonData = JSON.parse(inputData);
254
+
255
+ if (!Array.isArray(jsonData)) {
256
+ throw new Error('JSON data must be an array of objects');
257
+ }
258
+
259
+ if (shouldLog) {
260
+ console.log(
261
+ color(
262
+ `Converting ${jsonData.length.toLocaleString()} records...`,
263
+ 'dim'
264
+ )
265
+ );
266
+ }
267
+
268
+ if (options.transform) {
269
+ if (shouldLog) {
270
+ console.log(
271
+ color(`Applying transform from: ${options.transform}`, 'dim')
272
+ );
273
+ }
255
274
  let transformedData;
256
275
  try {
257
276
  transformedData = transformLoader.applyTransform(
258
277
  jsonData,
259
278
  options.transform
260
279
  );
261
- if (!options.silent) {
262
- console.log(
263
- color(
264
- `✓ Transform applied to ${transformedData.length} records`,
265
- 'green'
266
- )
267
- );
268
- }
280
+ if (shouldLog) {
281
+ console.log(
282
+ color(
283
+ `✓ Transform applied to ${transformedData.length} records`,
284
+ 'green'
285
+ )
286
+ );
287
+ }
269
288
  } catch (transformError: any) {
270
289
  console.error(
271
290
  color(`✗ Transform error: ${transformError.message}`, 'red')
@@ -278,19 +297,20 @@ try {
278
297
  }
279
298
 
280
299
  // Prepare options for jtcsv
281
- const jtcsvOptions = {
282
- delimiter: options.delimiter,
283
- includeHeaders: options.includeHeaders,
284
- renameMap: options.renameMap,
285
- template: options.template,
286
- maxRecords: options.maxRecords,
287
- preventCsvInjection: options.preventCsvInjection,
288
- rfc4180Compliant: options.rfc4180Compliant,
289
- schema: options.schema, // Add schema option
290
- flatten: options.flatten,
291
- flattenSeparator: options.flattenSeparator,
292
- flattenMaxDepth: options.flattenMaxDepth,
293
- arrayHandling: options.arrayHandling
300
+ const jtcsvOptions = {
301
+ delimiter: options.delimiter,
302
+ includeHeaders: options.includeHeaders,
303
+ renameMap: options.renameMap,
304
+ template: options.template,
305
+ maxRecords: options.maxRecords,
306
+ preventCsvInjection: options.preventCsvInjection,
307
+ rfc4180Compliant: options.rfc4180Compliant,
308
+ normalizeQuotes: options.normalizeQuotes,
309
+ schema: options.schema, // Add schema option
310
+ flatten: options.flatten,
311
+ flattenSeparator: options.flattenSeparator,
312
+ flattenMaxDepth: options.flattenMaxDepth,
313
+ arrayHandling: options.arrayHandling
294
314
  };
295
315
 
296
316
  // Apply transform function if provided
@@ -302,24 +322,30 @@ try {
302
322
  // Convert to CSV
303
323
  const csvData = jtcsv.jsonToCsv(transformedData, jtcsvOptions);
304
324
 
305
- // Write output file
306
- await fs.promises.writeFile(outputFile, csvData, 'utf8');
307
-
308
- const elapsed = Date.now() - startTime;
309
- if (!options.silent) {
310
- console.log(
311
- color(
312
- `✓ Converted ${transformedData.length.toLocaleString()} records in ${elapsed}ms`,
313
- 'green'
314
- )
315
- );
316
- console.log(
317
- color(
318
- ` Output: ${outputFile} (${csvData.length.toLocaleString()} bytes)`,
319
- 'dim'
320
- )
321
- );
322
- }
325
+ // Write output
326
+ if (useStdout) {
327
+ process.stdout.write(csvData);
328
+ } else {
329
+ await fs.promises.writeFile(outputFile, csvData, 'utf8');
330
+ }
331
+
332
+ const elapsed = Date.now() - startTime;
333
+ if (shouldLog) {
334
+ console.log(
335
+ color(
336
+ `✓ Converted ${transformedData.length.toLocaleString()} records in ${elapsed}ms`,
337
+ 'green'
338
+ )
339
+ );
340
+ if (!useStdout) {
341
+ console.log(
342
+ color(
343
+ ` Output: ${outputFile} (${csvData.length.toLocaleString()} bytes)`,
344
+ 'dim'
345
+ )
346
+ );
347
+ }
348
+ }
323
349
 
324
350
  return {
325
351
  records: transformedData.length,
@@ -336,12 +362,15 @@ try {
336
362
  }
337
363
 
338
364
  async function convertCsvToJson(inputFile, outputFile, options: any): Promise<ConversionResult> {
339
- const startTime = Date.now();
340
-
341
- try {
342
- if (!options.silent) {
343
- console.log(color('Reading CSV file...', 'dim'));
344
- }
365
+ const startTime = Date.now();
366
+ const useStdin = inputFile === '-';
367
+ const useStdout = outputFile === '-';
368
+ const shouldLog = !options.silent && !useStdout;
369
+
370
+ try {
371
+ if (shouldLog) {
372
+ console.log(color('Reading CSV file...', 'dim'));
373
+ }
345
374
 
346
375
  // Prepare options for jtcsv
347
376
  const jtcsvOptions = {
@@ -351,38 +380,46 @@ async function convertCsvToJson(inputFile, outputFile, options: any): Promise<Co
351
380
  hasHeaders: options.hasHeaders,
352
381
  renameMap: options.renameMap,
353
382
  trim: options.trim,
354
- parseNumbers: options.parseNumbers,
355
- parseBooleans: options.parseBooleans,
356
- maxRows: options.maxRows,
357
- useFastPath: options.useFastPath,
358
- fastPathMode: options.fastPathMode,
359
- schema: options.schema // Add schema option if supported
360
- };
361
-
362
- // Read and convert CSV
363
- const jsonData = await jtcsv.readCsvAsJson(inputFile, jtcsvOptions);
383
+ parseNumbers: options.parseNumbers,
384
+ parseBooleans: options.parseBooleans,
385
+ maxRows: options.maxRows,
386
+ useFastPath: options.useFastPath,
387
+ fastPathMode: options.fastPathMode,
388
+ repairRowShifts: options.repairRowShifts,
389
+ normalizeQuotes: options.normalizeQuotes,
390
+ schema: options.schema // Add schema option if supported
391
+ };
392
+
393
+ // Read and convert CSV
394
+ let jsonData: any;
395
+ if (useStdin) {
396
+ const csvContent = await readStdin();
397
+ jsonData = jtcsv.csvToJson(csvContent, jtcsvOptions);
398
+ } else {
399
+ jsonData = await jtcsv.readCsvAsJson(inputFile, jtcsvOptions);
400
+ }
364
401
 
365
402
  // Apply transform if specified
366
403
  let transformedData = jsonData;
367
- if (options.transform) {
368
- if (!options.silent) {
369
- console.log(
370
- color(`Applying transform from: ${options.transform}`, 'dim')
371
- );
372
- }
404
+ if (options.transform) {
405
+ if (shouldLog) {
406
+ console.log(
407
+ color(`Applying transform from: ${options.transform}`, 'dim')
408
+ );
409
+ }
373
410
  try {
374
411
  transformedData = transformLoader.applyTransform(
375
412
  jsonData,
376
413
  options.transform
377
414
  );
378
- if (!options.silent) {
379
- console.log(
380
- color(
381
- `✓ Transform applied to ${transformedData.length} rows`,
382
- 'green'
383
- )
384
- );
385
- }
415
+ if (shouldLog) {
416
+ console.log(
417
+ color(
418
+ `✓ Transform applied to ${transformedData.length} rows`,
419
+ 'green'
420
+ )
421
+ );
422
+ }
386
423
  } catch (transformError: any) {
387
424
  console.error(
388
425
  color(`✗ Transform error: ${transformError.message}`, 'red')
@@ -399,24 +436,30 @@ async function convertCsvToJson(inputFile, outputFile, options: any): Promise<Co
399
436
  ? JSON.stringify(transformedData, null, 2)
400
437
  : JSON.stringify(transformedData);
401
438
 
402
- // Write output file
403
- await fs.promises.writeFile(outputFile, jsonOutput, 'utf8');
404
-
405
- const elapsed = Date.now() - startTime;
406
- if (!options.silent) {
407
- console.log(
408
- color(
409
- `✓ Converted ${transformedData.length.toLocaleString()} rows in ${elapsed}ms`,
410
- 'green'
411
- )
412
- );
413
- console.log(
414
- color(
415
- ` Output: ${outputFile} (${jsonOutput.length.toLocaleString()} bytes)`,
416
- 'dim'
417
- )
418
- );
419
- }
439
+ // Write output
440
+ if (useStdout) {
441
+ process.stdout.write(jsonOutput);
442
+ } else {
443
+ await fs.promises.writeFile(outputFile, jsonOutput, 'utf8');
444
+ }
445
+
446
+ const elapsed = Date.now() - startTime;
447
+ if (shouldLog) {
448
+ console.log(
449
+ color(
450
+ `✓ Converted ${transformedData.length.toLocaleString()} rows in ${elapsed}ms`,
451
+ 'green'
452
+ )
453
+ );
454
+ if (!useStdout) {
455
+ console.log(
456
+ color(
457
+ ` Output: ${outputFile} (${jsonOutput.length.toLocaleString()} bytes)`,
458
+ 'dim'
459
+ )
460
+ );
461
+ }
462
+ }
420
463
 
421
464
  return {
422
465
  rows: transformedData.length,
@@ -453,14 +496,16 @@ async function saveAsCsv(inputFile, outputFile, options: any): Promise<Conversio
453
496
  }
454
497
  try {
455
498
  // Для CSV нужно сначала распарсить, применить трансформацию, затем снова сериализовать
456
- const parsedData = jtcsv.csvToJson(inputData, {
457
- delimiter: options.delimiter,
458
- autoDetect: options.autoDetect,
459
- hasHeaders: options.hasHeaders,
460
- trim: options.trim,
461
- parseNumbers: options.parseNumbers,
462
- parseBooleans: options.parseBooleans
463
- });
499
+ const parsedData = jtcsv.csvToJson(inputData, {
500
+ delimiter: options.delimiter,
501
+ autoDetect: options.autoDetect,
502
+ hasHeaders: options.hasHeaders,
503
+ trim: options.trim,
504
+ parseNumbers: options.parseNumbers,
505
+ parseBooleans: options.parseBooleans,
506
+ repairRowShifts: options.repairRowShifts,
507
+ normalizeQuotes: options.normalizeQuotes
508
+ });
464
509
 
465
510
  const transformedJson = transformLoader.applyTransform(
466
511
  parsedData,
@@ -468,10 +513,11 @@ async function saveAsCsv(inputFile, outputFile, options: any): Promise<Conversio
468
513
  );
469
514
 
470
515
  // Конвертировать обратно в CSV
471
- transformedData = jtcsv.jsonToCsv(transformedJson, {
472
- delimiter: options.delimiter,
473
- includeHeaders: options.includeHeaders
474
- });
516
+ transformedData = jtcsv.jsonToCsv(transformedJson, {
517
+ delimiter: options.delimiter,
518
+ includeHeaders: options.includeHeaders,
519
+ normalizeQuotes: options.normalizeQuotes
520
+ });
475
521
 
476
522
  if (!options.silent) {
477
523
  console.log(color('✓ Transform applied', 'green'));
@@ -597,14 +643,15 @@ async function convertNdjsonToCsv(inputFile, outputFile, options: any): Promise<
597
643
  }
598
644
 
599
645
  // Prepare options for jtcsv
600
- const jtcsvOptions = {
601
- delimiter: options.delimiter,
602
- includeHeaders: options.includeHeaders,
603
- renameMap: options.renameMap,
604
- template: options.template,
605
- preventCsvInjection: options.preventCsvInjection,
606
- rfc4180Compliant: options.rfc4180Compliant
607
- };
646
+ const jtcsvOptions = {
647
+ delimiter: options.delimiter,
648
+ includeHeaders: options.includeHeaders,
649
+ renameMap: options.renameMap,
650
+ template: options.template,
651
+ preventCsvInjection: options.preventCsvInjection,
652
+ rfc4180Compliant: options.rfc4180Compliant,
653
+ normalizeQuotes: options.normalizeQuotes
654
+ };
608
655
 
609
656
  // Convert to CSV
610
657
  const csvData = jtcsv.jsonToCsv(jsonData, jtcsvOptions);
@@ -1866,10 +1913,12 @@ function parseOptions(args: any): any {
1866
1913
  template: undefined,
1867
1914
  trim: true,
1868
1915
  parseNumbers: false,
1869
- parseBooleans: false,
1870
- useFastPath: true,
1871
- fastPathMode: 'objects',
1872
- preventCsvInjection: true,
1916
+ parseBooleans: false,
1917
+ useFastPath: true,
1918
+ fastPathMode: 'objects',
1919
+ repairRowShifts: true,
1920
+ normalizeQuotes: true,
1921
+ preventCsvInjection: true,
1873
1922
  rfc4180Compliant: true,
1874
1923
  maxRecords: undefined,
1875
1924
  maxRows: undefined,
@@ -1882,11 +1931,13 @@ function parseOptions(args: any): any {
1882
1931
  addBOM: false,
1883
1932
  unwrapArrays: false,
1884
1933
  stringifyObjects: false,
1885
- recursive: false,
1886
- pattern: '**/*',
1887
- outputDir: './output',
1888
- overwrite: false,
1889
- parallel: 4,
1934
+ recursive: false,
1935
+ pattern: '**/*',
1936
+ outputDir: './output',
1937
+ patternProvided: false,
1938
+ outputDirProvided: false,
1939
+ overwrite: false,
1940
+ parallel: 4,
1890
1941
  chunkSize: 65536,
1891
1942
  bufferSize: 1000,
1892
1943
  schema: undefined,
@@ -1938,16 +1989,22 @@ function parseOptions(args: any): any {
1938
1989
  case 'fast-path':
1939
1990
  options.useFastPath = value !== 'false';
1940
1991
  break;
1941
- case 'fast-path-mode':
1942
- options.fastPathMode = value || 'objects';
1943
- if (
1944
- options.fastPathMode !== 'objects' &&
1945
- options.fastPathMode !== 'compact'
1946
- ) {
1947
- throw new Error('Invalid --fast-path-mode value (objects|compact)');
1948
- }
1949
- break;
1950
- case 'rename':
1992
+ case 'fast-path-mode':
1993
+ options.fastPathMode = value || 'objects';
1994
+ if (
1995
+ options.fastPathMode !== 'objects' &&
1996
+ options.fastPathMode !== 'compact'
1997
+ ) {
1998
+ throw new Error('Invalid --fast-path-mode value (objects|compact)');
1999
+ }
2000
+ break;
2001
+ case 'repair-row-shifts':
2002
+ options.repairRowShifts = true;
2003
+ break;
2004
+ case 'normalize-quotes':
2005
+ options.normalizeQuotes = true;
2006
+ break;
2007
+ case 'rename':
1951
2008
  try {
1952
2009
  const jsonStr = value || '{}';
1953
2010
  const cleanStr = jsonStr
@@ -2026,12 +2083,14 @@ function parseOptions(args: any): any {
2026
2083
  case 'recursive':
2027
2084
  options.recursive = true;
2028
2085
  break;
2029
- case 'pattern':
2030
- options.pattern = value || '**/*';
2031
- break;
2032
- case 'output-dir':
2033
- options.outputDir = value || './output';
2034
- break;
2086
+ case 'pattern':
2087
+ options.pattern = value || '**/*';
2088
+ options.patternProvided = true;
2089
+ break;
2090
+ case 'output-dir':
2091
+ options.outputDir = value || './output';
2092
+ options.outputDirProvided = true;
2093
+ break;
2035
2094
  case 'overwrite':
2036
2095
  options.overwrite = true;
2037
2096
  break;
@@ -2065,10 +2124,10 @@ function parseOptions(args: any): any {
2065
2124
  options.host = value || 'localhost';
2066
2125
  break;
2067
2126
  }
2068
- } else if (!arg.startsWith('-')) {
2069
- files.push(arg);
2070
- }
2071
- }
2127
+ } else if (arg === '-' || !arg.startsWith('-')) {
2128
+ files.push(arg);
2129
+ }
2130
+ }
2072
2131
 
2073
2132
  return { options, files };
2074
2133
  }
@@ -2453,20 +2512,37 @@ async function main(): Promise<void> {
2453
2512
 
2454
2513
  const batchCommand = args[1].toLowerCase();
2455
2514
  // Для batch команд нужно парсить опции начиная с 3-го аргумента (после batch и подкоманды)
2456
- const batchArgs = args.slice(2);
2457
- const { options: batchOptions, files: batchFiles } =
2458
- parseOptions(batchArgs);
2459
-
2460
- if (batchCommand === 'json-to-csv' && batchFiles.length >= 2) {
2461
- await batchJsonToCsv(batchFiles[0], batchFiles[1], batchOptions);
2462
- } else if (batchCommand === 'csv-to-json' && batchFiles.length >= 2) {
2463
- await batchCsvToJson(batchFiles[0], batchFiles[1], batchOptions);
2464
- } else if (batchCommand === 'process' && files.length >= 2) {
2465
- await batchProcessMixed(files[0], files[1], options);
2466
- } else {
2467
- console.error(
2468
- color('Error: Invalid batch command or missing files', 'red')
2469
- );
2515
+ const batchArgs = args.slice(2);
2516
+ const { options: batchOptions, files: batchFiles } =
2517
+ parseOptions(batchArgs);
2518
+ const inputPattern = batchFiles[0]
2519
+ || (batchOptions.patternProvided ? batchOptions.pattern : undefined);
2520
+ const outputDir = batchFiles[1]
2521
+ || (batchOptions.outputDirProvided ? batchOptions.outputDir : undefined);
2522
+
2523
+ if (!inputPattern || !outputDir) {
2524
+ console.error(
2525
+ color('Error: Batch mode requires input pattern and output directory', 'red')
2526
+ );
2527
+ console.log(
2528
+ color('Usage: jtcsv batch [json-to-csv|csv-to-json|process] "<pattern>" "<outputDir>"', 'cyan')
2529
+ );
2530
+ console.log(
2531
+ color('Or: jtcsv batch <subcommand> --pattern="<pattern>" --output-dir="<outputDir>"', 'cyan')
2532
+ );
2533
+ process.exit(1);
2534
+ }
2535
+
2536
+ if (batchCommand === 'json-to-csv') {
2537
+ await batchJsonToCsv(inputPattern, outputDir, batchOptions);
2538
+ } else if (batchCommand === 'csv-to-json') {
2539
+ await batchCsvToJson(inputPattern, outputDir, batchOptions);
2540
+ } else if (batchCommand === 'process') {
2541
+ await batchProcessMixed(inputPattern, outputDir, batchOptions);
2542
+ } else {
2543
+ console.error(
2544
+ color('Error: Invalid batch command or missing files', 'red')
2545
+ );
2470
2546
  process.exit(1);
2471
2547
  }
2472
2548
  break;
@@ -2532,3 +2608,5 @@ if (require.main === module) {
2532
2608
  }
2533
2609
 
2534
2610
  module.exports = { main };
2611
+
2612
+