jtcsv 2.2.8 → 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 (246) hide show
  1. package/README.md +204 -115
  2. package/bin/jtcsv.ts +2612 -0
  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 +1736 -0
  23. package/dist/jtcsv-core.cjs.js.map +1 -0
  24. package/dist/jtcsv-core.esm.js +1708 -0
  25. package/dist/jtcsv-core.esm.js.map +1 -0
  26. package/dist/jtcsv-core.umd.js +1742 -0
  27. package/dist/jtcsv-core.umd.js.map +1 -0
  28. package/dist/jtcsv-full.cjs.js +2241 -0
  29. package/dist/jtcsv-full.cjs.js.map +1 -0
  30. package/dist/jtcsv-full.esm.js +2209 -0
  31. package/dist/jtcsv-full.esm.js.map +1 -0
  32. package/dist/jtcsv-full.umd.js +2247 -0
  33. package/dist/jtcsv-full.umd.js.map +1 -0
  34. package/dist/jtcsv-workers.esm.js +768 -0
  35. package/dist/jtcsv-workers.esm.js.map +1 -0
  36. package/dist/jtcsv-workers.umd.js +782 -0
  37. package/dist/jtcsv-workers.umd.js.map +1 -0
  38. package/dist/jtcsv.cjs.js +1996 -2048
  39. package/dist/jtcsv.cjs.js.map +1 -1
  40. package/dist/jtcsv.esm.js +1992 -2048
  41. package/dist/jtcsv.esm.js.map +1 -1
  42. package/dist/jtcsv.umd.js +2157 -2209
  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/dist/src/web-server/index.js +648 -0
  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 +446 -0
  131. package/examples/advanced/csv-parser.worker.ts +89 -0
  132. package/examples/advanced/nested-objects-example.ts +306 -0
  133. package/examples/advanced/performance-optimization.ts +504 -0
  134. package/examples/advanced/run-demo-server.ts +116 -0
  135. package/examples/advanced/web-worker-usage.html +874 -0
  136. package/examples/async-multithreaded-example.ts +335 -0
  137. package/examples/cli-advanced-usage.md +290 -0
  138. package/examples/{cli-batch-processing.js → cli-batch-processing.ts} +38 -38
  139. package/examples/{cli-tool.js → cli-tool.ts} +5 -8
  140. package/examples/{error-handling.js → error-handling.ts} +356 -324
  141. package/examples/{express-api.js → express-api.ts} +161 -164
  142. package/examples/{large-dataset-example.js → large-dataset-example.ts} +201 -182
  143. package/examples/{ndjson-processing.js → ndjson-processing.ts} +456 -434
  144. package/examples/{plugin-excel-exporter.js → plugin-excel-exporter.ts} +6 -7
  145. package/examples/react-integration.tsx +637 -0
  146. package/examples/{schema-validation.js → schema-validation.ts} +2 -2
  147. package/examples/simple-usage.ts +194 -0
  148. package/examples/{streaming-example.js → streaming-example.ts} +12 -12
  149. package/index.d.ts +187 -18
  150. package/package.json +75 -81
  151. package/plugins.d.ts +37 -0
  152. package/schema.d.ts +103 -0
  153. package/src/browser/browser-functions.ts +402 -0
  154. package/src/browser/core.ts +152 -0
  155. package/src/browser/csv-to-json-browser.d.ts +3 -0
  156. package/src/browser/csv-to-json-browser.ts +494 -0
  157. package/src/browser/{errors-browser.js → errors-browser.ts} +305 -197
  158. package/src/browser/extensions/plugins.ts +93 -0
  159. package/src/browser/extensions/workers.ts +39 -0
  160. package/src/browser/globals.d.ts +5 -0
  161. package/src/browser/index.ts +192 -0
  162. package/src/browser/json-to-csv-browser.d.ts +3 -0
  163. package/src/browser/json-to-csv-browser.ts +338 -0
  164. package/src/browser/streams.ts +403 -0
  165. package/src/browser/workers/{csv-parser.worker.js → csv-parser.worker.ts} +3 -3
  166. package/src/browser/workers/{worker-pool.js → worker-pool.ts} +51 -30
  167. package/src/core/delimiter-cache.ts +320 -0
  168. package/src/core/{node-optimizations.js → node-optimizations.ts} +448 -407
  169. package/src/core/plugin-system.ts +588 -0
  170. package/src/core/transform-hooks.ts +566 -0
  171. package/src/engines/{fast-path-engine-new.js → fast-path-engine-new.ts} +11 -2
  172. package/src/engines/{fast-path-engine.js → fast-path-engine.ts} +79 -53
  173. package/src/errors.ts +1 -0
  174. package/src/formats/{ndjson-parser.js → ndjson-parser.ts} +24 -16
  175. package/src/formats/{tsv-parser.js → tsv-parser.ts} +18 -17
  176. package/src/{index-with-plugins.js → index-with-plugins.ts} +381 -357
  177. package/src/types/index.ts +275 -0
  178. package/src/utils/bom-utils.ts +373 -0
  179. package/src/utils/encoding-support.ts +155 -0
  180. package/src/utils/{schema-validator.js → schema-validator.ts} +814 -589
  181. package/src/utils/transform-loader.ts +389 -0
  182. package/src/utils/validators.ts +35 -0
  183. package/src/utils/zod-adapter.ts +280 -0
  184. package/src/web-server/{index.js → index.ts} +19 -19
  185. package/src/workers/csv-multithreaded.ts +310 -0
  186. package/src/workers/csv-parser.worker.ts +227 -0
  187. package/src/workers/worker-pool.ts +409 -0
  188. package/bin/jtcsv.js +0 -2462
  189. package/csv-to-json.js +0 -688
  190. package/errors.js +0 -208
  191. package/examples/simple-usage.js +0 -282
  192. package/index.js +0 -68
  193. package/json-save.js +0 -254
  194. package/json-to-csv.js +0 -526
  195. package/plugins/README.md +0 -91
  196. package/plugins/express-middleware/README.md +0 -64
  197. package/plugins/express-middleware/example.js +0 -136
  198. package/plugins/express-middleware/index.d.ts +0 -114
  199. package/plugins/express-middleware/index.js +0 -360
  200. package/plugins/express-middleware/package.json +0 -52
  201. package/plugins/fastify-plugin/index.js +0 -406
  202. package/plugins/fastify-plugin/package.json +0 -55
  203. package/plugins/hono/README.md +0 -28
  204. package/plugins/hono/index.d.ts +0 -12
  205. package/plugins/hono/index.js +0 -36
  206. package/plugins/hono/package.json +0 -35
  207. package/plugins/nestjs/README.md +0 -35
  208. package/plugins/nestjs/index.d.ts +0 -25
  209. package/plugins/nestjs/index.js +0 -77
  210. package/plugins/nestjs/package.json +0 -37
  211. package/plugins/nextjs-api/README.md +0 -57
  212. package/plugins/nextjs-api/examples/ConverterComponent.jsx +0 -386
  213. package/plugins/nextjs-api/examples/api-convert.js +0 -69
  214. package/plugins/nextjs-api/index.js +0 -387
  215. package/plugins/nextjs-api/package.json +0 -63
  216. package/plugins/nextjs-api/route.js +0 -371
  217. package/plugins/nuxt/README.md +0 -24
  218. package/plugins/nuxt/index.js +0 -21
  219. package/plugins/nuxt/package.json +0 -35
  220. package/plugins/nuxt/runtime/composables/useJtcsv.js +0 -6
  221. package/plugins/nuxt/runtime/plugin.js +0 -6
  222. package/plugins/remix/README.md +0 -26
  223. package/plugins/remix/index.d.ts +0 -16
  224. package/plugins/remix/index.js +0 -62
  225. package/plugins/remix/package.json +0 -35
  226. package/plugins/sveltekit/README.md +0 -28
  227. package/plugins/sveltekit/index.d.ts +0 -17
  228. package/plugins/sveltekit/index.js +0 -54
  229. package/plugins/sveltekit/package.json +0 -33
  230. package/plugins/trpc/README.md +0 -25
  231. package/plugins/trpc/index.d.ts +0 -7
  232. package/plugins/trpc/index.js +0 -32
  233. package/plugins/trpc/package.json +0 -34
  234. package/src/browser/browser-functions.js +0 -219
  235. package/src/browser/csv-to-json-browser.js +0 -700
  236. package/src/browser/index.js +0 -113
  237. package/src/browser/json-to-csv-browser.js +0 -309
  238. package/src/browser/streams.js +0 -393
  239. package/src/core/delimiter-cache.js +0 -186
  240. package/src/core/plugin-system.js +0 -476
  241. package/src/core/transform-hooks.js +0 -350
  242. package/src/errors.js +0 -26
  243. package/src/utils/transform-loader.js +0 -205
  244. package/stream-csv-to-json.js +0 -542
  245. package/stream-json-to-csv.js +0 -464
  246. /package/examples/{web-workers-advanced.js → web-workers-advanced.ts} +0 -0
@@ -1,542 +0,0 @@
1
- /**
2
- * Stream CSV to JSON Converter - Node.js Module
3
- *
4
- * A streaming implementation for converting CSV data to JSON format
5
- * with memory-efficient processing for large files.
6
- *
7
- * @module stream-csv-to-json
8
- */
9
-
10
- const {
11
- ValidationError,
12
- SecurityError,
13
- FileSystemError,
14
- ParsingError,
15
- LimitError,
16
- ConfigurationError,
17
- safeExecute
18
- } = require('./errors');
19
-
20
- const { Transform, Writable } = require('stream');
21
- const { pipeline } = require('stream/promises');
22
-
23
- // Import schema validator from utils
24
- const { createSchemaValidators } = require('./src/utils/schema-validator');
25
-
26
- /**
27
- * Creates a transform stream that converts CSV chunks to JSON objects
28
- *
29
- * @param {Object} options - Configuration options
30
- * @param {string} [options.delimiter=';'] - CSV delimiter character
31
- * @param {boolean} [options.hasHeaders=true] - Whether CSV has headers row
32
- * @param {Object} [options.renameMap={}] - Map for renaming column headers { newKey: oldKey }
33
- * @param {boolean} [options.trim=true] - Trim whitespace from values
34
- * @param {boolean} [options.parseNumbers=false] - Parse numeric values
35
- * @param {boolean} [options.parseBooleans=false] - Parse boolean values
36
- * @param {number} [options.maxRows=Infinity] - Maximum number of rows to process
37
- * @param {Function} [options.transform] - Custom transform function for each row
38
- * @param {Object} [options.schema] - JSON schema for validation and formatting
39
- * @returns {Transform} Transform stream
40
- *
41
- * @example
42
- * const { createCsvToJsonStream } = require('./stream-csv-to-json');
43
- *
44
- * const transformStream = createCsvToJsonStream({
45
- * delimiter: ',',
46
- * parseNumbers: true,
47
- * parseBooleans: true
48
- * });
49
- *
50
- * // Pipe CSV text to JSON objects
51
- * csvReadableStream.pipe(transformStream).pipe(jsonWritableStream);
52
- */
53
- /* istanbul ignore next */
54
- function createCsvToJsonStream(options = {}) {
55
- return safeExecute(() => {
56
- /* istanbul ignore next */
57
- const opts = options && typeof options === 'object' ? options : {};
58
-
59
- const {
60
- delimiter = ';',
61
- hasHeaders = true,
62
- renameMap = {},
63
- trim = true,
64
- parseNumbers = false,
65
- parseBooleans = false,
66
- maxRows = Infinity,
67
- transform = null,
68
- schema = null
69
- } = opts;
70
-
71
- // Validate options
72
- if (delimiter && typeof delimiter !== 'string') {
73
- throw new ConfigurationError('Delimiter must be a string');
74
- }
75
-
76
- if (delimiter && delimiter.length !== 1) {
77
- throw new ConfigurationError('Delimiter must be a single character');
78
- }
79
-
80
- if (renameMap && typeof renameMap !== 'object') {
81
- throw new ConfigurationError('renameMap must be an object');
82
- }
83
-
84
- if (maxRows !== Infinity && (typeof maxRows !== 'number' || maxRows <= 0)) {
85
- throw new ConfigurationError('maxRows must be a positive number or Infinity');
86
- }
87
-
88
- if (transform && typeof transform !== 'function') {
89
- throw new ConfigurationError('transform must be a function');
90
- }
91
-
92
- if (schema && typeof schema !== 'object') {
93
- throw new ConfigurationError('schema must be an object');
94
- }
95
-
96
- let buffer = '';
97
- let headers = null;
98
- let headersProcessed = false;
99
- let rowCount = 0;
100
- let lineNumber = 0;
101
- let insideQuotes = false;
102
- let schemaValidators = null;
103
-
104
- // Initialize schema validators if schema is provided
105
- if (schema) {
106
- schemaValidators = createSchemaValidators(schema);
107
- }
108
-
109
- /**
110
- * Parses a CSV line with proper quote handling
111
- *
112
- * @private
113
- * @param {string} line - CSV line to parse
114
- * @returns {string[]} Array of field values
115
- */
116
- const parseCsvLine = (line) => {
117
- const fields = [];
118
- let currentField = '';
119
- let insideQuotesLocal = false;
120
- let i = 0;
121
-
122
- while (i < line.length) {
123
- const char = line[i];
124
-
125
- if (char === '"') {
126
- if (insideQuotesLocal) {
127
- // Check for escaped quote ("")
128
- if (i + 1 < line.length && line[i + 1] === '"') {
129
- currentField += '"';
130
- i++; // Skip next quote
131
- } else {
132
- insideQuotesLocal = false;
133
- }
134
- } else {
135
- insideQuotesLocal = true;
136
- }
137
- i++;
138
- continue;
139
- }
140
-
141
- if (!insideQuotesLocal && char === delimiter) {
142
- fields.push(currentField);
143
- currentField = '';
144
- i++;
145
- continue;
146
- }
147
-
148
- currentField += char;
149
- i++;
150
- }
151
-
152
- // Add last field
153
- fields.push(currentField);
154
-
155
- // Check for unclosed quotes
156
- if (insideQuotesLocal) {
157
- throw new ParsingError('Unclosed quotes in CSV', lineNumber);
158
- }
159
-
160
- return fields;
161
- };
162
-
163
- /**
164
- * Parses a CSV value based on options
165
- *
166
- * @private
167
- * @param {string} value - Raw CSV value
168
- * @returns {*} Parsed value
169
- */
170
- const parseCsvValue = (value) => {
171
- let result = value;
172
-
173
- if (trim) {
174
- result = result.trim();
175
- }
176
-
177
- // Remove Excel formula protection
178
- if (result.startsWith("'")) {
179
- result = result.substring(1);
180
- }
181
-
182
- // Parse numbers
183
- if (parseNumbers && /^-?\d+(\.\d+)?$/.test(result)) {
184
- const num = parseFloat(result);
185
- /* istanbul ignore next */
186
- if (!isNaN(num)) {
187
- return num;
188
- }
189
- }
190
-
191
- // Parse booleans
192
- if (parseBooleans) {
193
- const lowerValue = result.toLowerCase();
194
- if (lowerValue === 'true') {
195
- return true;
196
- }
197
- if (lowerValue === 'false') {
198
- return false;
199
- }
200
- }
201
-
202
- // Parse empty strings as null
203
- if (result === '') {
204
- return null;
205
- }
206
-
207
- return result;
208
- };
209
-
210
- /**
211
- * Formats value based on schema
212
- *
213
- * @private
214
- * @param {*} value - The value to format
215
- * @param {string} key - The key/field name
216
- * @returns {*} Formatted value
217
- */
218
- const formatValue = (value, key) => {
219
- if (!schemaValidators || !schemaValidators[key]) {
220
- return value;
221
- }
222
-
223
- const validator = schemaValidators[key];
224
-
225
- // Apply formatting if available
226
- if (validator.format) {
227
- return validator.format(value);
228
- }
229
-
230
- return value;
231
- };
232
-
233
- /**
234
- * Validates value against schema
235
- *
236
- * @private
237
- * @param {*} value - The value to validate
238
- * @param {string} key - The key/field name
239
- * @returns {boolean} True if valid
240
- */
241
- const validateValue = (value, key) => {
242
- if (!schemaValidators || !schemaValidators[key]) {
243
- return true;
244
- }
245
-
246
- const validator = schemaValidators[key];
247
-
248
- // Apply validation if available
249
- /* istanbul ignore next */
250
- if (validator.validate) {
251
- return validator.validate(value);
252
- }
253
-
254
- /* istanbul ignore next */
255
- return true;
256
- };
257
-
258
- /**
259
- * Processes a complete line of CSV
260
- *
261
- * @private
262
- * @param {string} line - Complete CSV line
263
- * @returns {Object|null} JSON object or null for headers
264
- */
265
- const processLine = (line) => {
266
- lineNumber++;
267
-
268
- // Skip empty lines
269
- if (line.trim() === '') {
270
- return null;
271
- }
272
-
273
- try {
274
- const fields = parseCsvLine(line);
275
-
276
- // Process headers
277
- if (hasHeaders && !headersProcessed) {
278
- headers = fields.map(field => {
279
- const trimmed = trim ? field.trim() : field;
280
- return renameMap[trimmed] || trimmed;
281
- });
282
- headersProcessed = true;
283
- return null;
284
- }
285
-
286
- // Generate headers if not provided
287
- if (!headers) {
288
- headers = fields.map((_, index) => `column${index + 1}`);
289
- }
290
-
291
- // Check row limit
292
- if (rowCount >= maxRows) {
293
- throw new LimitError(
294
- `CSV size exceeds maximum limit of ${maxRows} rows`,
295
- maxRows,
296
- rowCount
297
- );
298
- }
299
-
300
- // Build JSON object
301
- const row = {};
302
- const fieldCount = Math.min(fields.length, headers.length);
303
-
304
- for (let j = 0; j < fieldCount; j++) {
305
- let value = parseCsvValue(fields[j]);
306
- const key = headers[j];
307
-
308
- // Format value based on schema
309
- value = formatValue(value, key);
310
-
311
- // Validate value against schema
312
- if (!validateValue(value, key)) {
313
- throw new ValidationError(`Invalid value for field '${key}': ${value}`);
314
- }
315
-
316
- row[key] = value;
317
- }
318
-
319
- // Apply custom transform if provided
320
- let result = row;
321
- if (transform) {
322
- try {
323
- result = transform(row);
324
- /* istanbul ignore next */
325
- if (!result || typeof result !== 'object') {
326
- throw new ValidationError('Transform function must return an object');
327
- }
328
- } catch (error) {
329
- throw new ValidationError(`Transform function error: ${error.message}`);
330
- }
331
- }
332
-
333
- rowCount++;
334
- return result;
335
- } catch (error) {
336
- if (error instanceof ParsingError) {
337
- error.lineNumber = lineNumber;
338
- }
339
- throw error;
340
- }
341
- };
342
-
343
- return new Transform({
344
- objectMode: true,
345
- writableObjectMode: false,
346
- readableObjectMode: true,
347
-
348
- transform(chunk, encoding, callback) {
349
- try {
350
- const chunkStr = chunk.toString();
351
- buffer += chunkStr;
352
-
353
- // Process complete lines
354
- const lines = [];
355
- let start = 0;
356
-
357
- for (let i = 0; i < buffer.length; i++) {
358
- const char = buffer[i];
359
-
360
- if (char === '"') {
361
- insideQuotes = !insideQuotes;
362
- }
363
-
364
- if (!insideQuotes && char === '\n') {
365
- const line = buffer.substring(start, i).replace(/\r$/, '');
366
- lines.push(line);
367
- start = i + 1;
368
- }
369
- }
370
-
371
- // Keep incomplete line in buffer
372
- buffer = buffer.substring(start);
373
-
374
- // Process complete lines
375
- for (const line of lines) {
376
- const result = processLine(line);
377
- if (result !== null) {
378
- this.push(result);
379
- }
380
- }
381
-
382
- callback();
383
- } catch (error) {
384
- callback(error);
385
- }
386
- },
387
-
388
- /* istanbul ignore next */
389
- flush(callback) {
390
- try {
391
- // Process remaining buffer
392
- if (buffer.trim() !== '') {
393
- const result = processLine(buffer.replace(/\r$/, ''));
394
- if (result !== null) {
395
- this.push(result);
396
- }
397
- }
398
-
399
- // If no headers were found but were expected, generate them
400
- if (hasHeaders && !headersProcessed && headers === null) {
401
- // This means the CSV was empty or had no data rows
402
- // Nothing to do here
403
- }
404
-
405
- callback();
406
- } catch (error) {
407
- callback(error);
408
- }
409
- }
410
- });
411
- }, 'STREAM_CREATION_ERROR', { function: 'createCsvToJsonStream' });
412
- }
413
-
414
- /**
415
- * Converts a readable stream of CSV text to JSON objects
416
- *
417
- * @param {Readable} inputStream - Readable stream of CSV text
418
- * @param {Writable} outputStream - Writable stream for JSON objects
419
- * @param {Object} options - Configuration options (same as createCsvToJsonStream)
420
- * @returns {Promise<void>}
421
- *
422
- * @example
423
- * const { streamCsvToJson } = require('./stream-csv-to-json');
424
- *
425
- * await streamCsvToJson(csvStream, jsonStream, {
426
- * delimiter: ',',
427
- * parseNumbers: true,
428
- * schema: {
429
- * properties: {
430
- * id: { type: 'integer' },
431
- * name: { type: 'string', minLength: 1 }
432
- * }
433
- * }
434
- * });
435
- */
436
- /* istanbul ignore next */ async function streamCsvToJson(inputStream, outputStream, options = {}) {
437
- return safeExecute(async () => {
438
- const transformStream = createCsvToJsonStream(options);
439
-
440
- await pipeline(
441
- inputStream,
442
- transformStream,
443
- outputStream
444
- );
445
- }, 'STREAM_PROCESSING_ERROR', { function: 'streamCsvToJson' });
446
- }
447
-
448
- /**
449
- * Reads CSV file and converts it to JSON using streaming
450
- *
451
- * @param {string} filePath - Path to CSV file
452
- * @param {Object} options - Configuration options (same as createCsvToJsonStream)
453
- * @returns {Promise<Readable>} Readable stream of JSON objects
454
- *
455
- * @example
456
- * const { createCsvFileToJsonStream } = require('./stream-csv-to-json');
457
- *
458
- * const jsonStream = await createCsvFileToJsonStream('./large-data.csv', {
459
- * delimiter: ',',
460
- * parseNumbers: true
461
- * });
462
- *
463
- * jsonStream.pipe(process.stdout);
464
- */
465
- async function createCsvFileToJsonStream(filePath, options = {}) {
466
- return safeExecute(async () => {
467
- const fs = require('fs');
468
- const path = require('path');
469
-
470
- // Validate file path
471
- if (typeof filePath !== 'string' || filePath.trim() === '') {
472
- throw new ValidationError('File path must be a non-empty string');
473
- }
474
-
475
- if (!filePath.toLowerCase().endsWith('.csv')) {
476
- throw new ValidationError('File must have .csv extension');
477
- }
478
-
479
- // Prevent directory traversal attacks
480
- const normalizedPath = path.normalize(filePath);
481
- if (normalizedPath.includes('..') ||
482
- /\\\.\.\\|\/\.\.\//.test(filePath) ||
483
- filePath.startsWith('..') ||
484
- filePath.includes('/..')) {
485
- throw new SecurityError('Directory traversal detected in file path');
486
- }
487
-
488
- const safePath = path.resolve(filePath);
489
-
490
- // Check if file exists
491
- try {
492
- await fs.promises.access(safePath, fs.constants.R_OK);
493
- } catch (error) {
494
- throw new FileSystemError(`File not found or not readable: ${safePath}`, error);
495
- }
496
-
497
- // Create read stream
498
- const readStream = fs.createReadStream(safePath, 'utf8');
499
- const transformStream = createCsvToJsonStream(options);
500
-
501
- // Pipe through transform
502
- return readStream.pipe(transformStream);
503
- }, 'FILE_STREAM_ERROR', { function: 'createCsvFileToJsonStream' });
504
- }
505
-
506
- /**
507
- * Creates a writable stream that collects JSON objects into an array
508
- *
509
- * @returns {Writable} Writable stream that collects data
510
- */
511
- function createJsonCollectorStream() {
512
- const collectedData = [];
513
-
514
- return new Writable({
515
- objectMode: true,
516
-
517
- write(chunk, encoding, callback) {
518
- collectedData.push(chunk);
519
- callback();
520
- },
521
-
522
- final(callback) {
523
- this._collectedData = collectedData;
524
- callback();
525
- }
526
- });
527
- }
528
-
529
- module.exports = {
530
- createCsvToJsonStream,
531
- streamCsvToJson,
532
- createCsvFileToJsonStream,
533
- createJsonCollectorStream
534
- // Note: createSchemaValidators is no longer exported from here
535
- // It should be imported directly from './src/utils/schema-validator'
536
- };
537
-
538
- // For ES6 module compatibility
539
- /* istanbul ignore next */
540
- if (typeof module !== 'undefined' && module.exports) {
541
- module.exports.default = createCsvToJsonStream;
542
- }