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
@@ -0,0 +1,665 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCsvToJsonStream = createCsvToJsonStream;
4
+ exports.createJsonCollectorStream = createJsonCollectorStream;
5
+ exports.streamCsvToJson = streamCsvToJson;
6
+ exports.streamCsvToJsonAsync = streamCsvToJsonAsync;
7
+ exports.createCsvFileToJsonStream = createCsvFileToJsonStream;
8
+ const errors_1 = require("./errors");
9
+ const stream_1 = require("stream");
10
+ const promises_1 = require("stream/promises");
11
+ const schema_validator_1 = require("./src/utils/schema-validator");
12
+ const bom_utils_1 = require("./src/utils/bom-utils");
13
+ function createCsvToJsonStream(options = {}) {
14
+ return (0, errors_1.safeExecuteSync)(() => {
15
+ const { delimiter, autoDetect = true, candidates = [';', ',', '\t', '|'], hasHeaders = true, renameMap = {}, trim = true, parseNumbers = false, parseBooleans = false, maxRows = Infinity, transform: customTransform, schema, useFastPath = true, fastPathMode = 'objects', onError = 'throw', errorHandler, repairRowShifts = true, normalizeQuotes = true } = options;
16
+ if (delimiter !== undefined && typeof delimiter !== 'string') {
17
+ throw new errors_1.ConfigurationError('Delimiter must be a string');
18
+ }
19
+ if (delimiter && delimiter.length !== 1) {
20
+ throw new errors_1.ConfigurationError('Delimiter must be a single character');
21
+ }
22
+ if (typeof hasHeaders !== 'boolean') {
23
+ throw new errors_1.ConfigurationError('hasHeaders must be a boolean');
24
+ }
25
+ if (renameMap && typeof renameMap !== 'object') {
26
+ throw new errors_1.ConfigurationError('renameMap must be an object');
27
+ }
28
+ if (maxRows !== Infinity && (typeof maxRows !== 'number' || maxRows <= 0)) {
29
+ throw new errors_1.ConfigurationError('maxRows must be a positive number or Infinity');
30
+ }
31
+ if (customTransform !== undefined && typeof customTransform !== 'function') {
32
+ throw new errors_1.ConfigurationError('transform must be a function');
33
+ }
34
+ if (schema && typeof schema !== 'object') {
35
+ throw new errors_1.ConfigurationError('schema must be an object');
36
+ }
37
+ if (onError !== undefined && !['skip', 'warn', 'throw'].includes(onError)) {
38
+ throw new errors_1.ConfigurationError('onError must be "skip", "warn", or "throw"');
39
+ }
40
+ if (errorHandler !== undefined && typeof errorHandler !== 'function') {
41
+ throw new errors_1.ConfigurationError('errorHandler must be a function');
42
+ }
43
+ const schemaValidators = schema ? (0, schema_validator_1.createSchemaValidators)(schema) : null;
44
+ let buffer = '';
45
+ let headers = [];
46
+ let finalHeaders = [];
47
+ let headersProcessed = false;
48
+ let rowCount = 0;
49
+ let inputLineNumber = 0;
50
+ let finalDelimiter = delimiter;
51
+ let pendingRow = null;
52
+ let pendingRowLineNumber = null;
53
+ let pendingRowLine = null;
54
+ const normalizeValue = (value) => {
55
+ let normalized = value;
56
+ if (trim && typeof normalized === 'string') {
57
+ normalized = normalized.trim();
58
+ }
59
+ if (typeof normalized === 'string') {
60
+ if (normalized === '') {
61
+ return null;
62
+ }
63
+ if (normalized[0] === "'" && normalized.length > 1) {
64
+ const candidate = normalized.slice(1);
65
+ const leading = trim ? candidate.trimStart() : candidate;
66
+ const firstChar = leading[0];
67
+ if (firstChar === '=' || firstChar === '+' || firstChar === '-' || firstChar === '@') {
68
+ normalized = candidate;
69
+ }
70
+ }
71
+ }
72
+ if (parseNumbers && typeof normalized === 'string' && normalized.trim() !== '' && !isNaN(Number(normalized))) {
73
+ normalized = Number(normalized);
74
+ }
75
+ if (parseBooleans && normalized !== null && normalized !== undefined) {
76
+ const lowerValue = String(normalized).toLowerCase();
77
+ if (lowerValue === 'true' || lowerValue === 'false') {
78
+ normalized = lowerValue === 'true';
79
+ }
80
+ }
81
+ return normalized;
82
+ };
83
+ const isEmptyValue = (value) => value === undefined || value === null || value === '';
84
+ const hasOddQuotes = (value) => {
85
+ if (typeof value !== 'string') {
86
+ return false;
87
+ }
88
+ let count = 0;
89
+ for (let i = 0; i < value.length; i++) {
90
+ if (value[i] === '"') {
91
+ count++;
92
+ }
93
+ }
94
+ return count % 2 === 1;
95
+ };
96
+ const hasAnyQuotes = (value) => typeof value === 'string' && value.includes('"');
97
+ const normalizeQuotesInField = (value) => {
98
+ if (typeof value !== 'string') {
99
+ return value;
100
+ }
101
+ if ((value.startsWith('{') && value.endsWith('}')) ||
102
+ (value.startsWith('[') && value.endsWith(']'))) {
103
+ return value;
104
+ }
105
+ let normalized = value.replace(/"{2,}/g, '"');
106
+ normalized = normalized.replace(/"\n/g, '\n').replace(/\n"/g, '\n');
107
+ if (normalized.length >= 2 && normalized.startsWith('"') && normalized.endsWith('"')) {
108
+ normalized = normalized.slice(1, -1);
109
+ }
110
+ return normalized;
111
+ };
112
+ const normalizePhoneValue = (value) => {
113
+ if (typeof value !== 'string') {
114
+ return value;
115
+ }
116
+ const trimmed = value.trim();
117
+ if (trimmed === '') {
118
+ return trimmed;
119
+ }
120
+ return trimmed.replace(/["'\\]/g, '');
121
+ };
122
+ const normalizeRowQuotes = (row, headersList) => {
123
+ const normalized = {};
124
+ const phoneKeys = new Set(['phone', 'phonenumber', 'phone_number', 'tel', 'telephone']);
125
+ for (const header of headersList) {
126
+ const baseValue = normalizeQuotesInField(row[header]);
127
+ if (phoneKeys.has(String(header).toLowerCase())) {
128
+ normalized[header] = normalizePhoneValue(baseValue);
129
+ }
130
+ else {
131
+ normalized[header] = baseValue;
132
+ }
133
+ }
134
+ return normalized;
135
+ };
136
+ const looksLikeUserAgent = (value) => {
137
+ if (typeof value !== 'string') {
138
+ return false;
139
+ }
140
+ return /Mozilla\/|Opera\/|MSIE|AppleWebKit|Gecko|Safari|Chrome\//.test(value);
141
+ };
142
+ const isHexColor = (value) => typeof value === 'string' && /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(value);
143
+ const attemptMergeRows = (row, nextRow) => {
144
+ const headerCount = finalHeaders.length;
145
+ if (headerCount === 0) {
146
+ return null;
147
+ }
148
+ const values = finalHeaders.map((header) => row[header]);
149
+ let lastNonEmpty = -1;
150
+ for (let i = headerCount - 1; i >= 0; i--) {
151
+ if (!isEmptyValue(values[i])) {
152
+ lastNonEmpty = i;
153
+ break;
154
+ }
155
+ }
156
+ const missingCount = headerCount - 1 - lastNonEmpty;
157
+ if (lastNonEmpty >= 0 && missingCount > 0) {
158
+ const nextValues = finalHeaders.map((header) => nextRow[header]);
159
+ const nextTrailingEmpty = nextValues
160
+ .slice(headerCount - missingCount)
161
+ .every((value) => isEmptyValue(value));
162
+ const leadValues = nextValues
163
+ .slice(0, missingCount)
164
+ .filter((value) => !isEmptyValue(value));
165
+ const shouldMerge = nextTrailingEmpty
166
+ && leadValues.length > 0
167
+ && (hasOddQuotes(values[lastNonEmpty]) || hasAnyQuotes(values[lastNonEmpty]));
168
+ if (shouldMerge) {
169
+ const toAppend = leadValues.map((value) => String(value));
170
+ if (toAppend.length > 0) {
171
+ const base = isEmptyValue(values[lastNonEmpty]) ? '' : String(values[lastNonEmpty]);
172
+ values[lastNonEmpty] = base ? `${base}\n${toAppend.join('\n')}` : toAppend.join('\n');
173
+ }
174
+ for (let i = 0; i < missingCount; i++) {
175
+ values[lastNonEmpty + 1 + i] = nextValues[missingCount + i];
176
+ }
177
+ const merged = {};
178
+ for (let i = 0; i < headerCount; i++) {
179
+ merged[finalHeaders[i]] = values[i];
180
+ }
181
+ return merged;
182
+ }
183
+ }
184
+ if (headerCount >= 6) {
185
+ const nextValues = finalHeaders.map((header) => nextRow[header]);
186
+ const nextHex = nextValues[4];
187
+ const nextUserAgentHead = nextValues[2];
188
+ const nextUserAgentTail = nextValues[3];
189
+ const shouldMergeUserAgent = isEmptyValue(values[4])
190
+ && isEmptyValue(values[5])
191
+ && isHexColor(nextHex)
192
+ && (looksLikeUserAgent(nextUserAgentHead) || looksLikeUserAgent(nextUserAgentTail));
193
+ if (shouldMergeUserAgent) {
194
+ const addressParts = [values[3], nextValues[0], nextValues[1]]
195
+ .filter((value) => !isEmptyValue(value))
196
+ .map((value) => String(value));
197
+ values[3] = addressParts.join('\n');
198
+ const uaHead = isEmptyValue(nextUserAgentHead) ? '' : String(nextUserAgentHead);
199
+ const uaTail = isEmptyValue(nextUserAgentTail) ? '' : String(nextUserAgentTail);
200
+ const joiner = uaHead && uaTail ? (uaTail.startsWith(' ') ? '' : ',') : '';
201
+ values[4] = uaHead + joiner + uaTail;
202
+ values[5] = String(nextHex);
203
+ const merged = {};
204
+ for (let i = 0; i < headerCount; i++) {
205
+ merged[finalHeaders[i]] = values[i];
206
+ }
207
+ return merged;
208
+ }
209
+ }
210
+ return null;
211
+ };
212
+ const finalizeHeaders = (nextHeaders) => {
213
+ headers = nextHeaders;
214
+ finalHeaders = headers.map((header) => renameMap[header] || header);
215
+ headersProcessed = true;
216
+ };
217
+ const emitRow = (row, line, lineNumber, stream) => {
218
+ if (maxRows !== Infinity && rowCount >= maxRows) {
219
+ throw new errors_1.LimitError(`CSV size exceeds maximum limit of ${maxRows} rows`, maxRows, rowCount + 1);
220
+ }
221
+ let outputRow = row;
222
+ if (normalizeQuotes) {
223
+ outputRow = normalizeRowQuotes(outputRow, finalHeaders);
224
+ }
225
+ if (schemaValidators && Object.keys(schemaValidators).length > 0) {
226
+ for (const [field, validator] of Object.entries(schemaValidators)) {
227
+ const typedValidator = validator;
228
+ const value = outputRow[field];
229
+ if (typeof typedValidator.validate === 'function' && !typedValidator.validate(value)) {
230
+ throw new errors_1.ValidationError(`Invalid value for field "${field}"`);
231
+ }
232
+ if (typeof typedValidator.format === 'function') {
233
+ outputRow[field] = typedValidator.format(value);
234
+ }
235
+ }
236
+ }
237
+ if (customTransform) {
238
+ let transformed;
239
+ try {
240
+ transformed = customTransform(outputRow);
241
+ }
242
+ catch (error) {
243
+ throw new errors_1.ValidationError(`Transform function error: ${error.message}`);
244
+ }
245
+ if (!transformed || typeof transformed !== 'object') {
246
+ throw new errors_1.ValidationError('Transform function must return an object');
247
+ }
248
+ stream.push(transformed);
249
+ }
250
+ else {
251
+ stream.push(outputRow);
252
+ }
253
+ rowCount++;
254
+ };
255
+ const handleRowError = (error, line, lineNumber) => {
256
+ if (error instanceof errors_1.LimitError) {
257
+ throw error;
258
+ }
259
+ if (errorHandler) {
260
+ errorHandler(error, line, lineNumber);
261
+ }
262
+ if (onError === 'warn') {
263
+ if (process.env['NODE_ENV'] !== 'test') {
264
+ console.warn(`[jtcsv] Line ${lineNumber}: ${error.message}`);
265
+ }
266
+ return true;
267
+ }
268
+ if (onError === 'skip') {
269
+ return true;
270
+ }
271
+ throw error;
272
+ };
273
+ const transformStream = new stream_1.Transform({
274
+ readableObjectMode: true,
275
+ writableObjectMode: false,
276
+ transform(chunk, encoding, callback) {
277
+ try {
278
+ const chunkStr = chunk.toString();
279
+ buffer += chunkStr;
280
+ const lines = buffer.split('\n');
281
+ buffer = lines.pop() || '';
282
+ let errorLine = '';
283
+ let errorLineNumber = 0;
284
+ for (const line of lines) {
285
+ if (line.trim() === '') {
286
+ continue;
287
+ }
288
+ inputLineNumber += 1;
289
+ errorLine = line;
290
+ errorLineNumber = inputLineNumber;
291
+ if (rowCount >= maxRows) {
292
+ throw new errors_1.LimitError(`CSV size exceeds maximum limit of ${maxRows} rows`, maxRows, rowCount + 1);
293
+ }
294
+ try {
295
+ if (!finalDelimiter && autoDetect && !headersProcessed) {
296
+ finalDelimiter = autoDetectDelimiterFromLine(line, candidates);
297
+ }
298
+ if (!finalDelimiter) {
299
+ finalDelimiter = ';';
300
+ }
301
+ const values = parseCsvLine(line, finalDelimiter, trim, inputLineNumber);
302
+ if (!headersProcessed) {
303
+ if (hasHeaders) {
304
+ finalizeHeaders(values);
305
+ continue;
306
+ }
307
+ else {
308
+ finalizeHeaders(values.map((_, index) => `column${index + 1}`));
309
+ }
310
+ }
311
+ if (finalHeaders.length === 0) {
312
+ finalHeaders = headers.map((header) => renameMap[header] || header);
313
+ }
314
+ if (values.length !== finalHeaders.length) {
315
+ throw errors_1.ParsingError.fieldCountMismatch(finalHeaders.length, values.length, inputLineNumber, line);
316
+ }
317
+ const row = {};
318
+ for (let j = 0; j < finalHeaders.length; j++) {
319
+ const value = normalizeValue(values[j]);
320
+ row[finalHeaders[j]] = value;
321
+ }
322
+ if (repairRowShifts) {
323
+ if (!pendingRow) {
324
+ pendingRow = row;
325
+ pendingRowLineNumber = inputLineNumber;
326
+ pendingRowLine = line;
327
+ continue;
328
+ }
329
+ const merged = attemptMergeRows(pendingRow, row);
330
+ if (merged) {
331
+ const baseLine = pendingRowLine ?? line;
332
+ const baseLineNumber = pendingRowLineNumber ?? inputLineNumber;
333
+ pendingRow = null;
334
+ pendingRowLine = null;
335
+ pendingRowLineNumber = null;
336
+ errorLine = baseLine;
337
+ errorLineNumber = baseLineNumber;
338
+ emitRow(merged, baseLine, baseLineNumber, this);
339
+ }
340
+ else {
341
+ const baseLine = pendingRowLine ?? line;
342
+ const baseLineNumber = pendingRowLineNumber ?? inputLineNumber;
343
+ const rowToEmit = pendingRow;
344
+ pendingRow = row;
345
+ pendingRowLine = line;
346
+ pendingRowLineNumber = inputLineNumber;
347
+ errorLine = baseLine;
348
+ errorLineNumber = baseLineNumber;
349
+ emitRow(rowToEmit, baseLine, baseLineNumber, this);
350
+ }
351
+ }
352
+ else {
353
+ emitRow(row, line, inputLineNumber, this);
354
+ }
355
+ }
356
+ catch (error) {
357
+ if (!headersProcessed && hasHeaders) {
358
+ throw error;
359
+ }
360
+ if (handleRowError(error, errorLine, errorLineNumber)) {
361
+ continue;
362
+ }
363
+ }
364
+ }
365
+ callback();
366
+ }
367
+ catch (error) {
368
+ callback(error);
369
+ }
370
+ },
371
+ flush(callback) {
372
+ if (buffer.trim() !== '') {
373
+ let errorLine = buffer;
374
+ let errorLineNumber = inputLineNumber;
375
+ try {
376
+ if (rowCount >= maxRows) {
377
+ throw new errors_1.LimitError(`CSV size exceeds maximum limit of ${maxRows} rows`, maxRows, rowCount + 1);
378
+ }
379
+ if (rowCount < maxRows) {
380
+ inputLineNumber += 1;
381
+ errorLineNumber = inputLineNumber;
382
+ if (!finalDelimiter && autoDetect && !headersProcessed) {
383
+ finalDelimiter = autoDetectDelimiterFromLine(buffer, candidates);
384
+ }
385
+ if (!finalDelimiter) {
386
+ finalDelimiter = ';';
387
+ }
388
+ const values = parseCsvLine(buffer, finalDelimiter, trim, inputLineNumber);
389
+ if (!headersProcessed) {
390
+ if (hasHeaders) {
391
+ finalizeHeaders(values);
392
+ }
393
+ else {
394
+ finalizeHeaders(values.map((_, index) => `column${index + 1}`));
395
+ }
396
+ }
397
+ else {
398
+ if (finalHeaders.length === 0) {
399
+ finalHeaders = headers.map((header) => renameMap[header] || header);
400
+ }
401
+ if (values.length !== finalHeaders.length) {
402
+ throw errors_1.ParsingError.fieldCountMismatch(finalHeaders.length, values.length, inputLineNumber, buffer);
403
+ }
404
+ const row = {};
405
+ for (let j = 0; j < finalHeaders.length; j++) {
406
+ const value = normalizeValue(values[j]);
407
+ row[finalHeaders[j]] = value;
408
+ }
409
+ if (repairRowShifts) {
410
+ if (!pendingRow) {
411
+ pendingRow = row;
412
+ pendingRowLineNumber = inputLineNumber;
413
+ pendingRowLine = buffer;
414
+ }
415
+ else {
416
+ const merged = attemptMergeRows(pendingRow, row);
417
+ if (merged) {
418
+ const baseLine = pendingRowLine ?? buffer;
419
+ const baseLineNumber = pendingRowLineNumber ?? inputLineNumber;
420
+ pendingRow = null;
421
+ pendingRowLine = null;
422
+ pendingRowLineNumber = null;
423
+ errorLine = baseLine;
424
+ errorLineNumber = baseLineNumber;
425
+ emitRow(merged, baseLine, baseLineNumber, this);
426
+ }
427
+ else {
428
+ const baseLine = pendingRowLine ?? buffer;
429
+ const baseLineNumber = pendingRowLineNumber ?? inputLineNumber;
430
+ const rowToEmit = pendingRow;
431
+ pendingRow = row;
432
+ pendingRowLine = buffer;
433
+ pendingRowLineNumber = inputLineNumber;
434
+ errorLine = baseLine;
435
+ errorLineNumber = baseLineNumber;
436
+ emitRow(rowToEmit, baseLine, baseLineNumber, this);
437
+ }
438
+ }
439
+ }
440
+ else {
441
+ emitRow(row, buffer, inputLineNumber, this);
442
+ }
443
+ }
444
+ }
445
+ }
446
+ catch (error) {
447
+ if (!headersProcessed && hasHeaders) {
448
+ callback(error);
449
+ return;
450
+ }
451
+ try {
452
+ if (handleRowError(error, errorLine, errorLineNumber)) {
453
+ callback();
454
+ return;
455
+ }
456
+ }
457
+ catch (handledError) {
458
+ callback(handledError);
459
+ return;
460
+ }
461
+ }
462
+ }
463
+ if (pendingRow) {
464
+ const baseLine = pendingRowLine ?? '';
465
+ const baseLineNumber = pendingRowLineNumber ?? inputLineNumber;
466
+ try {
467
+ emitRow(pendingRow, baseLine, baseLineNumber, this);
468
+ }
469
+ catch (error) {
470
+ try {
471
+ if (handleRowError(error, baseLine, baseLineNumber)) {
472
+ callback();
473
+ return;
474
+ }
475
+ }
476
+ catch (handledError) {
477
+ callback(handledError);
478
+ return;
479
+ }
480
+ }
481
+ pendingRow = null;
482
+ pendingRowLine = null;
483
+ pendingRowLineNumber = null;
484
+ }
485
+ callback();
486
+ }
487
+ });
488
+ return transformStream;
489
+ }, 'STREAM_CREATION_ERROR', { function: 'createCsvToJsonStream' });
490
+ }
491
+ function createJsonCollectorStream(options = {}) {
492
+ return (0, errors_1.safeExecuteSync)(() => {
493
+ const collectedData = [];
494
+ const transformStream = new stream_1.Transform({
495
+ writableObjectMode: true,
496
+ readableObjectMode: false,
497
+ transform(chunk, encoding, callback) {
498
+ collectedData.push(chunk);
499
+ transformStream._collectedData = collectedData;
500
+ callback();
501
+ },
502
+ flush(callback) {
503
+ this.push(JSON.stringify(collectedData, null, 2));
504
+ transformStream._collectedData = collectedData;
505
+ callback();
506
+ }
507
+ });
508
+ transformStream._collectedData = collectedData;
509
+ if (Object.keys(options).length > 0) {
510
+ const csvToJsonStream = createCsvToJsonStream(options);
511
+ csvToJsonStream.pipe(transformStream);
512
+ return csvToJsonStream;
513
+ }
514
+ return transformStream;
515
+ }, 'STREAM_CREATION_ERROR', { function: 'createJsonCollectorStream' });
516
+ }
517
+ async function streamCsvToJson(csvOrStream, outputOrOptions = {}, options = {}) {
518
+ return (0, errors_1.safeExecuteAsync)(async () => {
519
+ const isReadableStream = (value) => value instanceof stream_1.Readable || (value && typeof value.pipe === 'function');
520
+ const isWritableStream = (value) => value instanceof stream_1.Writable || (value && typeof value.write === 'function');
521
+ if (isReadableStream(csvOrStream) && isWritableStream(outputOrOptions)) {
522
+ const csvToJsonStream = createCsvToJsonStream(options);
523
+ await (0, promises_1.pipeline)(csvOrStream, csvToJsonStream, outputOrOptions);
524
+ return;
525
+ }
526
+ const streamOptions = outputOrOptions || {};
527
+ const readableStream = new stream_1.Readable({
528
+ read() {
529
+ this.push(csvOrStream);
530
+ this.push(null);
531
+ }
532
+ });
533
+ const csvToJsonStream = createCsvToJsonStream(streamOptions);
534
+ const collectorStream = createJsonCollectorStream();
535
+ await (0, promises_1.pipeline)(readableStream, csvToJsonStream, collectorStream);
536
+ return collectorStream._collectedData || [];
537
+ }, 'STREAM_PROCESSING_ERROR', { function: 'streamCsvToJson' });
538
+ }
539
+ async function streamCsvToJsonAsync(csv, options = {}) {
540
+ return (0, errors_1.safeExecuteAsync)(async () => {
541
+ const { useWorkers = false, workerCount, chunkSize, onProgress, ...streamOptions } = options;
542
+ return streamCsvToJson(csv, streamOptions);
543
+ }, 'STREAM_PROCESSING_ERROR', { function: 'streamCsvToJsonAsync' });
544
+ }
545
+ async function createCsvFileToJsonStream(filePath, options = {}) {
546
+ return (0, errors_1.safeExecuteAsync)(async () => {
547
+ const { validatePath = true, ...streamOptions } = options;
548
+ const fs = require('fs');
549
+ const path = require('path');
550
+ let safePath = filePath;
551
+ if (validatePath) {
552
+ if (typeof filePath !== 'string' || filePath.trim().length === 0) {
553
+ throw new errors_1.ValidationError('File path must be a non-empty string');
554
+ }
555
+ const normalized = path.normalize(filePath.trim());
556
+ const traversalPattern = /(^|[\\/])\.\.([\\/]|$)/;
557
+ if (traversalPattern.test(normalized)) {
558
+ throw new errors_1.SecurityError('Directory traversal detected in file path');
559
+ }
560
+ if (path.extname(normalized).toLowerCase() !== '.csv') {
561
+ throw new errors_1.ValidationError('File must have .csv extension');
562
+ }
563
+ safePath = normalized;
564
+ }
565
+ try {
566
+ await fs.promises.access(safePath);
567
+ }
568
+ catch (error) {
569
+ if (error?.code === 'ENOENT') {
570
+ throw new errors_1.FileSystemError(`File not found: ${safePath}`, error);
571
+ }
572
+ throw error;
573
+ }
574
+ const fileStream = fs.createReadStream(safePath, 'utf8');
575
+ const csvToJsonStream = createCsvToJsonStream(streamOptions);
576
+ const bomStripStream = (0, bom_utils_1.createBomStripStream)();
577
+ const { pipeline } = require('stream');
578
+ const { PassThrough } = require('stream');
579
+ const outputStream = new PassThrough({ objectMode: true });
580
+ pipeline(fileStream, bomStripStream, csvToJsonStream, outputStream, (error) => {
581
+ if (error) {
582
+ outputStream.emit('error', error);
583
+ }
584
+ });
585
+ return outputStream;
586
+ }, 'STREAM_CREATION_ERROR', { function: 'createCsvFileToJsonStream' });
587
+ }
588
+ function autoDetectDelimiterFromLine(line, candidates) {
589
+ let bestDelimiter = candidates[0];
590
+ let bestScore = -1;
591
+ for (const delimiter of candidates) {
592
+ let score = 0;
593
+ for (let i = 0; i < line.length; i++) {
594
+ if (line[i] === delimiter) {
595
+ score++;
596
+ }
597
+ }
598
+ const fields = line.split(delimiter);
599
+ let hasQuotes = false;
600
+ let consistent = true;
601
+ for (const field of fields) {
602
+ if (field.includes('"') || field.includes("'")) {
603
+ hasQuotes = true;
604
+ }
605
+ }
606
+ if (!hasQuotes && fields.length > 1) {
607
+ score += 10;
608
+ }
609
+ if (score > bestScore) {
610
+ bestScore = score;
611
+ bestDelimiter = delimiter;
612
+ }
613
+ }
614
+ return bestDelimiter;
615
+ }
616
+ function parseCsvLine(line, delimiter, trim, lineNumber) {
617
+ const result = [];
618
+ let currentField = '';
619
+ let inQuotes = false;
620
+ let quoteChar = '"';
621
+ let escapeNext = false;
622
+ for (let i = 0; i < line.length; i++) {
623
+ const char = line[i];
624
+ const nextChar = i < line.length - 1 ? line[i + 1] : '';
625
+ if (escapeNext) {
626
+ currentField += char;
627
+ escapeNext = false;
628
+ continue;
629
+ }
630
+ if (char === '\\') {
631
+ escapeNext = true;
632
+ continue;
633
+ }
634
+ if (!inQuotes && char === delimiter) {
635
+ result.push(trim ? currentField.trim() : currentField);
636
+ currentField = '';
637
+ }
638
+ else if (!inQuotes && (char === '"' || char === "'")) {
639
+ inQuotes = true;
640
+ quoteChar = char;
641
+ }
642
+ else if (inQuotes && char === quoteChar && nextChar === quoteChar) {
643
+ currentField += char;
644
+ if (i + 2 === line.length) {
645
+ inQuotes = false;
646
+ }
647
+ i++;
648
+ }
649
+ else if (inQuotes && char === quoteChar) {
650
+ inQuotes = false;
651
+ }
652
+ else {
653
+ currentField += char;
654
+ }
655
+ }
656
+ if (escapeNext) {
657
+ currentField += '\\';
658
+ }
659
+ result.push(trim ? currentField.trim() : currentField);
660
+ if (inQuotes) {
661
+ throw errors_1.ParsingError.unclosedQuotes(lineNumber ?? null, null, line.substring(0, 100));
662
+ }
663
+ return result;
664
+ }
665
+ //# sourceMappingURL=stream-csv-to-json.js.map