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
@@ -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