jtcsv 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/README.md +205 -146
  2. package/bin/jtcsv.ts +280 -202
  3. package/browser.d.ts +142 -0
  4. package/dist/benchmark.js +446 -0
  5. package/dist/benchmark.js.map +1 -0
  6. package/dist/bin/jtcsv.js +1940 -0
  7. package/dist/bin/jtcsv.js.map +1 -0
  8. package/dist/csv-to-json.js +1262 -0
  9. package/dist/csv-to-json.js.map +1 -0
  10. package/dist/errors.js +291 -0
  11. package/dist/errors.js.map +1 -0
  12. package/dist/eslint.config.js +147 -0
  13. package/dist/eslint.config.js.map +1 -0
  14. package/dist/index-core.js +95 -0
  15. package/dist/index-core.js.map +1 -0
  16. package/dist/index.js +93 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/json-save.js +229 -0
  19. package/dist/json-save.js.map +1 -0
  20. package/dist/json-to-csv.js +576 -0
  21. package/dist/json-to-csv.js.map +1 -0
  22. package/dist/jtcsv-core.cjs.js +336 -7
  23. package/dist/jtcsv-core.cjs.js.map +1 -1
  24. package/dist/jtcsv-core.esm.js +336 -7
  25. package/dist/jtcsv-core.esm.js.map +1 -1
  26. package/dist/jtcsv-core.umd.js +336 -7
  27. package/dist/jtcsv-core.umd.js.map +1 -1
  28. package/dist/jtcsv-full.cjs.js +336 -7
  29. package/dist/jtcsv-full.cjs.js.map +1 -1
  30. package/dist/jtcsv-full.esm.js +336 -7
  31. package/dist/jtcsv-full.esm.js.map +1 -1
  32. package/dist/jtcsv-full.umd.js +336 -7
  33. package/dist/jtcsv-full.umd.js.map +1 -1
  34. package/dist/jtcsv-workers.esm.js +9 -0
  35. package/dist/jtcsv-workers.esm.js.map +1 -1
  36. package/dist/jtcsv-workers.umd.js +9 -0
  37. package/dist/jtcsv-workers.umd.js.map +1 -1
  38. package/dist/jtcsv.cjs.js +1998 -2092
  39. package/dist/jtcsv.cjs.js.map +1 -1
  40. package/dist/jtcsv.esm.js +1994 -2092
  41. package/dist/jtcsv.esm.js.map +1 -1
  42. package/dist/jtcsv.umd.js +2157 -2251
  43. package/dist/jtcsv.umd.js.map +1 -1
  44. package/dist/plugins/express-middleware/index.js +350 -0
  45. package/dist/plugins/express-middleware/index.js.map +1 -0
  46. package/dist/plugins/fastify-plugin/index.js +315 -0
  47. package/dist/plugins/fastify-plugin/index.js.map +1 -0
  48. package/dist/plugins/hono/index.js +111 -0
  49. package/dist/plugins/hono/index.js.map +1 -0
  50. package/dist/plugins/nestjs/index.js +112 -0
  51. package/dist/plugins/nestjs/index.js.map +1 -0
  52. package/dist/plugins/nuxt/index.js +53 -0
  53. package/dist/plugins/nuxt/index.js.map +1 -0
  54. package/dist/plugins/remix/index.js +133 -0
  55. package/dist/plugins/remix/index.js.map +1 -0
  56. package/dist/plugins/sveltekit/index.js +155 -0
  57. package/dist/plugins/sveltekit/index.js.map +1 -0
  58. package/dist/plugins/trpc/index.js +136 -0
  59. package/dist/plugins/trpc/index.js.map +1 -0
  60. package/dist/run-demo.js +49 -0
  61. package/dist/run-demo.js.map +1 -0
  62. package/dist/src/browser/browser-functions.js +193 -0
  63. package/dist/src/browser/browser-functions.js.map +1 -0
  64. package/dist/src/browser/core.js +123 -0
  65. package/dist/src/browser/core.js.map +1 -0
  66. package/dist/src/browser/csv-to-json-browser.js +353 -0
  67. package/dist/src/browser/csv-to-json-browser.js.map +1 -0
  68. package/dist/src/browser/errors-browser.js +219 -0
  69. package/dist/src/browser/errors-browser.js.map +1 -0
  70. package/dist/src/browser/extensions/plugins.js +106 -0
  71. package/dist/src/browser/extensions/plugins.js.map +1 -0
  72. package/dist/src/browser/extensions/workers.js +66 -0
  73. package/dist/src/browser/extensions/workers.js.map +1 -0
  74. package/dist/src/browser/index.js +140 -0
  75. package/dist/src/browser/index.js.map +1 -0
  76. package/dist/src/browser/json-to-csv-browser.js +225 -0
  77. package/dist/src/browser/json-to-csv-browser.js.map +1 -0
  78. package/dist/src/browser/streams.js +340 -0
  79. package/dist/src/browser/streams.js.map +1 -0
  80. package/dist/src/browser/workers/csv-parser.worker.js +264 -0
  81. package/dist/src/browser/workers/csv-parser.worker.js.map +1 -0
  82. package/dist/src/browser/workers/worker-pool.js +338 -0
  83. package/dist/src/browser/workers/worker-pool.js.map +1 -0
  84. package/dist/src/core/delimiter-cache.js +196 -0
  85. package/dist/src/core/delimiter-cache.js.map +1 -0
  86. package/dist/src/core/node-optimizations.js +279 -0
  87. package/dist/src/core/node-optimizations.js.map +1 -0
  88. package/dist/src/core/plugin-system.js +399 -0
  89. package/dist/src/core/plugin-system.js.map +1 -0
  90. package/dist/src/core/transform-hooks.js +348 -0
  91. package/dist/src/core/transform-hooks.js.map +1 -0
  92. package/dist/src/engines/fast-path-engine-new.js +262 -0
  93. package/dist/src/engines/fast-path-engine-new.js.map +1 -0
  94. package/dist/src/engines/fast-path-engine.js +671 -0
  95. package/dist/src/engines/fast-path-engine.js.map +1 -0
  96. package/dist/src/errors.js +18 -0
  97. package/dist/src/errors.js.map +1 -0
  98. package/dist/src/formats/ndjson-parser.js +332 -0
  99. package/dist/src/formats/ndjson-parser.js.map +1 -0
  100. package/dist/src/formats/tsv-parser.js +230 -0
  101. package/dist/src/formats/tsv-parser.js.map +1 -0
  102. package/dist/src/index-with-plugins.js +259 -0
  103. package/dist/src/index-with-plugins.js.map +1 -0
  104. package/dist/src/types/index.js +3 -0
  105. package/dist/src/types/index.js.map +1 -0
  106. package/dist/src/utils/bom-utils.js +267 -0
  107. package/dist/src/utils/bom-utils.js.map +1 -0
  108. package/dist/src/utils/encoding-support.js +77 -0
  109. package/dist/src/utils/encoding-support.js.map +1 -0
  110. package/dist/src/utils/schema-validator.js +609 -0
  111. package/dist/src/utils/schema-validator.js.map +1 -0
  112. package/dist/src/utils/transform-loader.js +281 -0
  113. package/dist/src/utils/transform-loader.js.map +1 -0
  114. package/dist/src/utils/validators.js +40 -0
  115. package/dist/src/utils/validators.js.map +1 -0
  116. package/dist/src/utils/zod-adapter.js +144 -0
  117. package/dist/src/utils/zod-adapter.js.map +1 -0
  118. package/{src → dist/src}/web-server/index.js +251 -286
  119. package/dist/src/web-server/index.js.map +1 -0
  120. package/dist/src/workers/csv-multithreaded.js +211 -0
  121. package/dist/src/workers/csv-multithreaded.js.map +1 -0
  122. package/dist/src/workers/csv-parser.worker.js +179 -0
  123. package/dist/src/workers/csv-parser.worker.js.map +1 -0
  124. package/dist/src/workers/worker-pool.js +228 -0
  125. package/dist/src/workers/worker-pool.js.map +1 -0
  126. package/dist/stream-csv-to-json.js +665 -0
  127. package/dist/stream-csv-to-json.js.map +1 -0
  128. package/dist/stream-json-to-csv.js +389 -0
  129. package/dist/stream-json-to-csv.js.map +1 -0
  130. package/examples/advanced/conditional-transformations.ts +2 -2
  131. package/examples/advanced/performance-optimization.ts +2 -2
  132. package/examples/cli-advanced-usage.md +2 -0
  133. package/examples/cli-tool.ts +1 -1
  134. package/examples/large-dataset-example.ts +2 -2
  135. package/examples/simple-usage.ts +2 -2
  136. package/examples/streaming-example.ts +1 -1
  137. package/index.d.ts +186 -15
  138. package/package.json +43 -108
  139. package/plugins.d.ts +37 -0
  140. package/schema.d.ts +103 -0
  141. package/src/browser/csv-to-json-browser.ts +233 -3
  142. package/src/browser/errors-browser.ts +45 -28
  143. package/src/browser/json-to-csv-browser.ts +81 -5
  144. package/src/browser/streams.ts +73 -6
  145. package/src/core/delimiter-cache.ts +21 -11
  146. package/src/core/plugin-system.ts +343 -155
  147. package/src/core/transform-hooks.ts +20 -12
  148. package/src/engines/fast-path-engine.ts +48 -32
  149. package/src/errors.ts +1 -72
  150. package/src/formats/ndjson-parser.ts +6 -0
  151. package/src/formats/tsv-parser.ts +6 -0
  152. package/src/types/index.ts +21 -1
  153. package/src/utils/validators.ts +35 -0
  154. package/src/web-server/index.ts +1 -1
  155. package/bin/jtcsv.js +0 -2532
  156. package/csv-to-json.js +0 -711
  157. package/errors.js +0 -394
  158. package/examples/advanced/conditional-transformations.js +0 -446
  159. package/examples/advanced/csv-parser.worker.js +0 -89
  160. package/examples/advanced/nested-objects-example.js +0 -306
  161. package/examples/advanced/performance-optimization.js +0 -504
  162. package/examples/advanced/run-demo-server.js +0 -116
  163. package/examples/cli-batch-processing.js +0 -38
  164. package/examples/cli-tool.js +0 -183
  165. package/examples/error-handling.js +0 -338
  166. package/examples/express-api.js +0 -164
  167. package/examples/large-dataset-example.js +0 -182
  168. package/examples/ndjson-processing.js +0 -434
  169. package/examples/plugin-excel-exporter.js +0 -406
  170. package/examples/schema-validation.js +0 -640
  171. package/examples/simple-usage.js +0 -282
  172. package/examples/streaming-example.js +0 -418
  173. package/examples/web-workers-advanced.js +0 -28
  174. package/index.js +0 -82
  175. package/json-save.js +0 -255
  176. package/json-to-csv.js +0 -668
  177. package/plugins/README.md +0 -91
  178. package/plugins/express-middleware/README.md +0 -83
  179. package/plugins/express-middleware/example.js +0 -135
  180. package/plugins/express-middleware/example.ts +0 -135
  181. package/plugins/express-middleware/index.d.ts +0 -114
  182. package/plugins/express-middleware/index.js +0 -512
  183. package/plugins/express-middleware/index.ts +0 -557
  184. package/plugins/express-middleware/package.json +0 -52
  185. package/plugins/fastify-plugin/index.js +0 -404
  186. package/plugins/fastify-plugin/index.ts +0 -443
  187. package/plugins/fastify-plugin/package.json +0 -55
  188. package/plugins/hono/README.md +0 -28
  189. package/plugins/hono/index.d.ts +0 -12
  190. package/plugins/hono/index.js +0 -36
  191. package/plugins/hono/index.ts +0 -226
  192. package/plugins/hono/package.json +0 -35
  193. package/plugins/nestjs/README.md +0 -35
  194. package/plugins/nestjs/index.d.ts +0 -25
  195. package/plugins/nestjs/index.js +0 -77
  196. package/plugins/nestjs/index.ts +0 -201
  197. package/plugins/nestjs/package.json +0 -37
  198. package/plugins/nextjs-api/README.md +0 -57
  199. package/plugins/nextjs-api/examples/ConverterComponent.jsx +0 -386
  200. package/plugins/nextjs-api/examples/ConverterComponent.tsx +0 -386
  201. package/plugins/nextjs-api/examples/api-convert.js +0 -67
  202. package/plugins/nextjs-api/examples/api-convert.ts +0 -67
  203. package/plugins/nextjs-api/index.js +0 -387
  204. package/plugins/nextjs-api/index.tsx +0 -339
  205. package/plugins/nextjs-api/package.json +0 -63
  206. package/plugins/nextjs-api/route.js +0 -370
  207. package/plugins/nextjs-api/route.ts +0 -370
  208. package/plugins/nuxt/README.md +0 -24
  209. package/plugins/nuxt/index.js +0 -21
  210. package/plugins/nuxt/index.ts +0 -94
  211. package/plugins/nuxt/package.json +0 -35
  212. package/plugins/nuxt/runtime/composables/useJtcsv.js +0 -6
  213. package/plugins/nuxt/runtime/composables/useJtcsv.ts +0 -100
  214. package/plugins/nuxt/runtime/plugin.js +0 -6
  215. package/plugins/nuxt/runtime/plugin.ts +0 -71
  216. package/plugins/remix/README.md +0 -26
  217. package/plugins/remix/index.d.ts +0 -16
  218. package/plugins/remix/index.js +0 -62
  219. package/plugins/remix/index.ts +0 -260
  220. package/plugins/remix/package.json +0 -35
  221. package/plugins/sveltekit/README.md +0 -28
  222. package/plugins/sveltekit/index.d.ts +0 -17
  223. package/plugins/sveltekit/index.js +0 -54
  224. package/plugins/sveltekit/index.ts +0 -301
  225. package/plugins/sveltekit/package.json +0 -33
  226. package/plugins/trpc/README.md +0 -25
  227. package/plugins/trpc/index.d.ts +0 -7
  228. package/plugins/trpc/index.js +0 -32
  229. package/plugins/trpc/index.ts +0 -267
  230. package/plugins/trpc/package.json +0 -34
  231. package/src/browser/browser-functions.js +0 -219
  232. package/src/browser/core.js +0 -92
  233. package/src/browser/csv-to-json-browser.js +0 -722
  234. package/src/browser/errors-browser.js +0 -212
  235. package/src/browser/extensions/plugins.js +0 -92
  236. package/src/browser/extensions/workers.js +0 -39
  237. package/src/browser/index.js +0 -113
  238. package/src/browser/json-to-csv-browser.js +0 -319
  239. package/src/browser/streams.js +0 -403
  240. package/src/browser/workers/csv-parser.worker.js +0 -377
  241. package/src/browser/workers/worker-pool.js +0 -527
  242. package/src/core/delimiter-cache.js +0 -200
  243. package/src/core/node-optimizations.js +0 -408
  244. package/src/core/plugin-system.js +0 -494
  245. package/src/core/transform-hooks.js +0 -350
  246. package/src/engines/fast-path-engine-new.js +0 -338
  247. package/src/engines/fast-path-engine.js +0 -844
  248. package/src/errors.js +0 -26
  249. package/src/formats/ndjson-parser.js +0 -467
  250. package/src/formats/tsv-parser.js +0 -339
  251. package/src/index-with-plugins.js +0 -378
  252. package/src/utils/bom-utils.js +0 -259
  253. package/src/utils/encoding-support.js +0 -124
  254. package/src/utils/schema-validator.js +0 -594
  255. package/src/utils/transform-loader.js +0 -205
  256. package/src/utils/zod-adapter.js +0 -170
  257. package/stream-csv-to-json.js +0 -560
  258. package/stream-json-to-csv.js +0 -465
package/csv-to-json.js DELETED
@@ -1,711 +0,0 @@
1
- // @ts-nocheck
2
- /**
3
- * CSV to JSON Converter - Node.js Module
4
- *
5
- * A lightweight, efficient module for converting CSV data to JSON format
6
- * with proper parsing and error handling.
7
- *
8
- * @module csv-to-json
9
- */
10
-
11
- const {
12
- ValidationError,
13
- SecurityError,
14
- FileSystemError,
15
- ParsingError,
16
- LimitError,
17
- ConfigurationError,
18
- safeExecute
19
- } = require('./errors');
20
-
21
- const { TransformHooks, predefinedHooks } = require('./src/core/transform-hooks');
22
- const DelimiterCache = require('./src/core/delimiter-cache');
23
- const FastPathEngine = require('./src/engines/fast-path-engine');
24
- const { stripBomFromString, normalizeCsvInput } = require('./src/utils/bom-utils');
25
-
26
- // Глобальный экземпляр кэша для авто-детектирования разделителя
27
- const globalDelimiterCache = new DelimiterCache(100);
28
- const globalFastPathEngine = new FastPathEngine();
29
-
30
- /**
31
- * Validates CSV input and options
32
- * @private
33
- */
34
- function validateCsvInput(csv, options) {
35
- // Validate CSV input
36
- if (typeof csv !== 'string') {
37
- throw new ValidationError('Input must be a CSV string');
38
- }
39
-
40
- // Validate options
41
- if (options && typeof options !== 'object') {
42
- throw new ConfigurationError('Options must be an object');
43
- }
44
-
45
- // Validate delimiter
46
- if (options?.delimiter && typeof options.delimiter !== 'string') {
47
- throw new ConfigurationError('Delimiter must be a string');
48
- }
49
-
50
- if (options?.delimiter && options.delimiter.length !== 1) {
51
- throw new ConfigurationError('Delimiter must be a single character');
52
- }
53
-
54
- // Validate autoDetect
55
- if (options?.autoDetect !== undefined && typeof options.autoDetect !== 'boolean') {
56
- throw new ConfigurationError('autoDetect must be a boolean');
57
- }
58
-
59
- // Validate candidates
60
- if (options?.candidates && !Array.isArray(options.candidates)) {
61
- throw new ConfigurationError('candidates must be an array');
62
- }
63
-
64
- // Validate maxRows
65
- if (options?.maxRows !== undefined && (typeof options.maxRows !== 'number' || options.maxRows <= 0)) {
66
- throw new ConfigurationError('maxRows must be a positive number');
67
- }
68
-
69
- // Validate cache options
70
- if (options?.useCache !== undefined && typeof options.useCache !== 'boolean') {
71
- throw new ConfigurationError('useCache must be a boolean');
72
- }
73
-
74
- if (options?.cache && !(options.cache instanceof DelimiterCache)) {
75
- throw new ConfigurationError('cache must be an instance of DelimiterCache');
76
- }
77
-
78
- if (options?.useFastPath !== undefined && typeof options.useFastPath !== 'boolean') {
79
- throw new ConfigurationError('useFastPath must be a boolean');
80
- }
81
-
82
- if (options?.fastPathMode !== undefined
83
- && options.fastPathMode !== 'objects'
84
- && options.fastPathMode !== 'compact'
85
- && options.fastPathMode !== 'stream') {
86
- throw new ConfigurationError('fastPathMode must be "objects", "compact", or "stream"');
87
- }
88
-
89
- // Validate hooks
90
- if (options?.hooks) {
91
- if (typeof options.hooks !== 'object') {
92
- throw new ConfigurationError('hooks must be an object');
93
- }
94
-
95
- if (options.hooks.beforeConvert && typeof options.hooks.beforeConvert !== 'function') {
96
- throw new ConfigurationError('hooks.beforeConvert must be a function');
97
- }
98
-
99
- if (options.hooks.afterConvert && typeof options.hooks.afterConvert !== 'function') {
100
- throw new ConfigurationError('hooks.afterConvert must be a function');
101
- }
102
-
103
- if (options.hooks.perRow && typeof options.hooks.perRow !== 'function') {
104
- throw new ConfigurationError('hooks.perRow must be a function');
105
- }
106
-
107
- if (options.hooks.transformHooks && !(options.hooks.transformHooks instanceof TransformHooks)) {
108
- throw new ConfigurationError('hooks.transformHooks must be an instance of TransformHooks');
109
- }
110
- }
111
-
112
- return true;
113
- }
114
-
115
- /**
116
- * Parses a value based on options
117
- * @private
118
- */
119
- function parseCsvValue(value, options) {
120
- /* istanbul ignore next */
121
- const { trim = true, parseNumbers = false, parseBooleans = false } = options;
122
-
123
- let result = value;
124
-
125
- if (trim) {
126
- result = result.trim();
127
- }
128
-
129
- // Remove Excel formula protection
130
- if (result.startsWith("'")) {
131
- result = result.substring(1);
132
- }
133
-
134
- // Parse numbers
135
- if (parseNumbers) {
136
- // Fast numeric detection: check first character and use parseFloat
137
- const trimmed = result.trim();
138
- const firstChar = trimmed.charAt(0);
139
- // Quick reject: if first character is not digit, minus, or dot, skip
140
- if ((firstChar >= '0' && firstChar <= '9') || firstChar === '-' || firstChar === '.') {
141
- const num = parseFloat(trimmed);
142
- if (!isNaN(num) && isFinite(num)) {
143
- // Ensure the whole string represents the same number (no extra characters)
144
- // parseFloat ignores trailing non-numeric characters, so we need to verify
145
- // that the string matches the parsed number when converted back
146
- if (String(num) === trimmed || (trimmed.includes('.') && !isNaN(Number(trimmed)))) {
147
- return num;
148
- }
149
- }
150
- }
151
- }
152
-
153
- // Parse booleans
154
- if (parseBooleans) {
155
- const lowerValue = result.toLowerCase();
156
- if (lowerValue === 'true') {
157
- return true;
158
- }
159
- if (lowerValue === 'false') {
160
- return false;
161
- }
162
- }
163
-
164
- // Parse empty strings as null
165
- if (result === '') {
166
- return null;
167
- }
168
-
169
- return result;
170
- }
171
-
172
- /**
173
- * Auto-detect CSV delimiter from content with caching support
174
- * @private
175
- */
176
- function autoDetectDelimiter(csv, candidates = [';', ',', '\t', '|'], cache = null) {
177
- // Используем статический метод DelimiterCache с поддержкой кэширования
178
- return DelimiterCache.autoDetectDelimiter(csv, candidates, cache);
179
- }
180
-
181
- /**
182
- * Converts CSV string to JSON array with hooks and caching support
183
- *
184
- * @param {string} csv - CSV string to convert
185
- * @param {Object} [options] - Configuration options
186
- * @param {string} [options.delimiter] - CSV delimiter character (default: auto-detected)
187
- * @param {boolean} [options.autoDetect=true] - Auto-detect delimiter if not specified
188
- * @param {Array} [options.candidates=[';', ',', '\t', '|']] - Candidate delimiters for auto-detection
189
- * @param {boolean} [options.hasHeaders=true] - Whether CSV has headers row
190
- * @param {Object} [options.renameMap={}] - Map for renaming column headers (newKey: oldKey)
191
- * @param {boolean} [options.trim=true] - Trim whitespace from values
192
- * @param {boolean} [options.parseNumbers=false] - Parse numeric values
193
- * @param {boolean} [options.parseBooleans=false] - Parse boolean values
194
- * @param {number} [options.maxRows] - Maximum number of rows to process (optional, no limit by default)
195
- * @param {boolean} [options.useCache=true] - Use caching for delimiter detection
196
- * @param {DelimiterCache} [options.cache] - Custom cache instance (optional)
197
- * @param {Object} [options.hooks] - Transform hooks
198
- * @param {Function} [options.hooks.beforeConvert] - Hook called before conversion
199
- * @param {Function} [options.hooks.afterConvert] - Hook called after conversion
200
- * @param {Function} [options.hooks.perRow] - Hook called for each row
201
- * @param {TransformHooks} [options.hooks.transformHooks] - TransformHooks instance
202
- * @returns {Array<Object>} JSON array
203
- *
204
- * @example
205
- * const { csvToJson } = require('./csv-to-json');
206
- *
207
- * const csv = `id;name;email\n1;John;john@example.com\n2;Jane;jane@example.com`;
208
- * const json = csvToJson(csv, {
209
- * delimiter: ';',
210
- * parseNumbers: true,
211
- * useCache: true, // Включить кэширование
212
- * hooks: {
213
- * beforeConvert: (data) => {
214
- * console.log('Starting conversion...');
215
- * return data;
216
- * },
217
- * perRow: (row, index) => {
218
- * return { ...row, processed: true, index };
219
- * },
220
- * afterConvert: (data) => {
221
- * return data.filter(item => item.id > 0);
222
- * }
223
- * }
224
- * });
225
- */
226
- function csvToJson(csv, options = {}) {
227
- return safeExecute(() => {
228
- // Validate input
229
- validateCsvInput(csv, options);
230
-
231
- const opts = options && typeof options === 'object' ? options : {};
232
-
233
- const {
234
- delimiter,
235
- autoDetect = true,
236
- candidates = [';', ',', '\t', '|'],
237
- hasHeaders = true,
238
- renameMap = {},
239
- trim = true,
240
- parseNumbers = false,
241
- parseBooleans = false,
242
- maxRows,
243
- useCache = true,
244
- cache: customCache,
245
- useFastPath = true,
246
- fastPathMode = 'objects',
247
- hooks = {},
248
- stripBom = true, // New option: strip BOM by default
249
- normalizeEncoding = true // New option: normalize encoding
250
- } = opts;
251
-
252
- if (fastPathMode === 'stream') {
253
- return csvToJsonIterator(csv, { ...opts, useFastPath, fastPathMode: 'objects' });
254
- }
255
-
256
- // Выбираем кэш для использования
257
- const cacheToUse = useCache ? (customCache || globalDelimiterCache) : null;
258
-
259
- // Create transform hooks system
260
- const transformHooks = new TransformHooks();
261
-
262
- // Add individual hooks if provided
263
- if (hooks.beforeConvert) {
264
- transformHooks.beforeConvert(hooks.beforeConvert);
265
- }
266
-
267
- if (hooks.afterConvert) {
268
- transformHooks.afterConvert(hooks.afterConvert);
269
- }
270
-
271
- if (hooks.perRow) {
272
- transformHooks.perRow(hooks.perRow);
273
- }
274
-
275
- // Use provided TransformHooks instance if available
276
- const finalHooks = hooks.transformHooks || transformHooks;
277
-
278
- // Handle BOM and encoding normalization
279
- let normalizedCsv = csv;
280
- if (stripBom) {
281
- normalizedCsv = stripBomFromString(normalizedCsv);
282
- }
283
- if (normalizeEncoding) {
284
- normalizedCsv = normalizeCsvInput(normalizedCsv, { normalizeLineEndings: true });
285
- }
286
-
287
- // Apply beforeConvert hooks to CSV string
288
- const processedCsv = finalHooks.applyBeforeConvert(normalizedCsv, {
289
- operation: 'csvToJson',
290
- options: opts
291
- });
292
-
293
- // Determine delimiter with caching support
294
- let finalDelimiter = delimiter;
295
- if (!finalDelimiter && autoDetect) {
296
- finalDelimiter = autoDetectDelimiter(processedCsv, candidates, cacheToUse);
297
- }
298
- finalDelimiter = finalDelimiter || ';'; // fallback
299
-
300
- // Handle empty CSV
301
- if (processedCsv.trim() === '') {
302
- return [];
303
- }
304
-
305
- let headers = null;
306
- let totalRows = 0;
307
- let dataRowIndex = 0;
308
- const result = [];
309
-
310
- try {
311
- const parseOptions = { delimiter: finalDelimiter };
312
- if (useFastPath === false) {
313
- parseOptions.forceEngine = 'STANDARD';
314
- }
315
-
316
- globalFastPathEngine.parseRows(processedCsv, parseOptions, (fields) => {
317
- totalRows++;
318
-
319
- if (!headers) {
320
- if (hasHeaders) {
321
- headers = fields.map(header => {
322
- const trimmed = trim ? header.trim() : header;
323
- return renameMap[trimmed] || trimmed;
324
- });
325
- return;
326
- }
327
- headers = fields.map((_, index) => `column${index + 1}`);
328
- }
329
-
330
- if (!fields || fields.length === 0) {
331
- return;
332
- }
333
-
334
- if (maxRows && totalRows > maxRows) {
335
- throw new LimitError(
336
- `CSV size exceeds maximum limit of ${maxRows} rows`,
337
- maxRows,
338
- totalRows
339
- );
340
- }
341
-
342
- const fieldCount = Math.min(fields.length, headers.length);
343
- let row;
344
-
345
- if (fastPathMode === 'compact') {
346
- row = new Array(fieldCount);
347
- for (let j = 0; j < fieldCount; j++) {
348
- row[j] = parseCsvValue(fields[j], { trim, parseNumbers, parseBooleans });
349
- }
350
- } else {
351
- row = {};
352
- for (let j = 0; j < fieldCount; j++) {
353
- row[headers[j]] = parseCsvValue(fields[j], { trim, parseNumbers, parseBooleans });
354
- }
355
- }
356
-
357
- const processedRow = finalHooks.applyPerRow(row, dataRowIndex, {
358
- lineNumber: totalRows,
359
- headers,
360
- options: opts
361
- });
362
-
363
- dataRowIndex++;
364
- result.push(processedRow);
365
-
366
- if (fields.length > headers.length && process.env.NODE_ENV === 'development') {
367
- console.warn(`[jtcsv] Line ${totalRows}: ${fields.length - headers.length} extra fields ignored`);
368
- }
369
- });
370
- } catch (error) {
371
- if (error && error.code === 'FAST_PATH_UNCLOSED_QUOTES') {
372
- const lineInfo = error.lineNumber ? ` at line ${error.lineNumber}` : '';
373
- throw new ParsingError(`Unclosed quotes in CSV${lineInfo}`, error.lineNumber);
374
- }
375
- throw error;
376
- }
377
-
378
- if (!headers) {
379
- return [];
380
- }
381
-
382
- if (totalRows > 1000000 && !maxRows && process.env.NODE_ENV !== 'test') {
383
- console.warn(
384
- 'Warning: Processing >1M records in memory may be slow.\n' +
385
- 'Consider using createCsvToJsonStream() for better performance with large files.\n' +
386
- 'Current size: ' + totalRows.toLocaleString() + ' rows\n' +
387
- 'Tip: Use { maxRows: N } option to set a custom limit if needed.'
388
- );
389
- }
390
-
391
- return finalHooks.applyAfterConvert(result, {
392
- operation: 'csvToJson',
393
- totalRows: result.length,
394
- options: opts
395
- });
396
-
397
- }, 'PARSE_FAILED', { function: 'csvToJson' });
398
- }
399
-
400
- /* istanbul ignore next */
401
- async function* csvToJsonIterator(csv, options = {}) {
402
- validateCsvInput(csv, options);
403
-
404
- const opts = options && typeof options === 'object' ? options : {};
405
-
406
- const {
407
- delimiter,
408
- autoDetect = true,
409
- candidates = [';', ',', '\t', '|'],
410
- hasHeaders = true,
411
- renameMap = {},
412
- trim = true,
413
- parseNumbers = false,
414
- parseBooleans = false,
415
- maxRows,
416
- useCache = true,
417
- cache: customCache,
418
- useFastPath = true,
419
- fastPathMode = 'objects',
420
- hooks = {}
421
- } = opts;
422
-
423
- const cacheToUse = useCache ? (customCache || globalDelimiterCache) : null;
424
-
425
- const transformHooks = new TransformHooks();
426
-
427
- if (hooks.beforeConvert) {
428
- transformHooks.beforeConvert(hooks.beforeConvert);
429
- }
430
-
431
- if (hooks.afterConvert) {
432
- transformHooks.afterConvert(hooks.afterConvert);
433
- }
434
-
435
- if (hooks.perRow) {
436
- transformHooks.perRow(hooks.perRow);
437
- }
438
-
439
- const finalHooks = hooks.transformHooks || transformHooks;
440
-
441
- const processedCsv = finalHooks.applyBeforeConvert(csv, {
442
- operation: 'csvToJson',
443
- options: opts
444
- });
445
-
446
- let finalDelimiter = delimiter;
447
- if (!finalDelimiter && autoDetect) {
448
- finalDelimiter = autoDetectDelimiter(processedCsv, candidates, cacheToUse);
449
- }
450
- finalDelimiter = finalDelimiter || ';';
451
-
452
- if (processedCsv.trim() === '') {
453
- return;
454
- }
455
-
456
- let headers = null;
457
- let totalRows = 0;
458
- let dataRowIndex = 0;
459
-
460
- const handleFields = (fields, lineNumber) => {
461
- if (!headers) {
462
- if (hasHeaders) {
463
- headers = fields.map(header => {
464
- const trimmed = trim ? header.trim() : header;
465
- return renameMap[trimmed] || trimmed;
466
- });
467
- return null;
468
- }
469
- headers = fields.map((_, index) => `column${index + 1}`);
470
- }
471
-
472
- if (!fields || fields.length === 0) {
473
- return null;
474
- }
475
-
476
- const fieldCount = Math.min(fields.length, headers.length);
477
- let row;
478
-
479
- const resolvedFastPathMode = fastPathMode === 'stream' ? 'objects' : fastPathMode;
480
-
481
- if (resolvedFastPathMode === 'compact') {
482
- row = new Array(fieldCount);
483
- for (let j = 0; j < fieldCount; j++) {
484
- row[j] = parseCsvValue(fields[j], { trim, parseNumbers, parseBooleans });
485
- }
486
- } else {
487
- row = {};
488
- for (let j = 0; j < fieldCount; j++) {
489
- row[headers[j]] = parseCsvValue(fields[j], { trim, parseNumbers, parseBooleans });
490
- }
491
- }
492
-
493
- const processedRow = finalHooks.applyPerRow(row, dataRowIndex, {
494
- lineNumber,
495
- headers,
496
- options: opts
497
- });
498
-
499
- dataRowIndex++;
500
- return processedRow;
501
- };
502
-
503
- try {
504
- const parseOptions = { delimiter: finalDelimiter };
505
- if (useFastPath === false) {
506
- parseOptions.forceEngine = 'STANDARD';
507
- }
508
-
509
- for (const fields of globalFastPathEngine.iterateRows(processedCsv, parseOptions)) {
510
- totalRows++;
511
- if (maxRows && totalRows > maxRows) {
512
- throw new LimitError(
513
- `CSV size exceeds maximum limit of ${maxRows} rows`,
514
- maxRows,
515
- totalRows
516
- );
517
- }
518
- const processedRow = handleFields(fields, totalRows);
519
- if (processedRow !== undefined && processedRow !== null) {
520
- if (fields.length > headers.length && process.env.NODE_ENV === 'development') {
521
- console.warn(`[jtcsv] Line ${totalRows}: ${fields.length - headers.length} extra fields ignored`);
522
- }
523
- yield processedRow;
524
- }
525
- }
526
- } catch (error) {
527
- if (error && error.code === 'FAST_PATH_UNCLOSED_QUOTES') {
528
- const lineInfo = error.lineNumber ? ` at line ${error.lineNumber}` : '';
529
- throw new ParsingError(`Unclosed quotes in CSV${lineInfo}`, error.lineNumber);
530
- }
531
- throw error;
532
- }
533
- }
534
-
535
- /**
536
- * Validates file path for CSV reading
537
- * @private
538
- */
539
- function validateCsvFilePath(filePath) {
540
- const path = require('path');
541
-
542
- // Basic validation
543
- if (typeof filePath !== 'string' || filePath.trim() === '') {
544
- throw new ValidationError('File path must be a non-empty string');
545
- }
546
-
547
- // Ensure file has .csv extension
548
- if (!filePath.toLowerCase().endsWith('.csv')) {
549
- throw new ValidationError('File must have .csv extension');
550
- }
551
-
552
- // Prevent directory traversal attacks
553
- const normalizedPath = path.normalize(filePath);
554
- if (normalizedPath.includes('..') ||
555
- /\\\\.\\.\\|\/\\.\\.\//.test(filePath) ||
556
- filePath.startsWith('..') ||
557
- filePath.includes('/..')) {
558
- throw new SecurityError('Directory traversal detected in file path');
559
- }
560
-
561
- return path.resolve(filePath);
562
- }
563
-
564
- /**
565
- * Reads CSV file and converts it to JSON array with hooks and caching support
566
- *
567
- * @param {string} filePath - Path to CSV file
568
- * @param {Object} [options] - Configuration options (same as csvToJson)
569
- * @returns {Promise<Array<Object>>} Promise that resolves to JSON array
570
- *
571
- * @example
572
- * const { readCsvAsJson } = require('./csv-to-json');
573
- *
574
- * const json = await readCsvAsJson('./data.csv', {
575
- * delimiter: ',',
576
- * parseNumbers: true,
577
- * useCache: true,
578
- * hooks: {
579
- * perRow: (row) => ({ ...row, processed: true })
580
- * }
581
- * });
582
- */
583
- async function readCsvAsJson(filePath, options = {}) {
584
- const fs = require('fs').promises;
585
-
586
- // Validate file path
587
- const safePath = validateCsvFilePath(filePath);
588
-
589
- try {
590
- // Read file
591
- const csvContent = await fs.readFile(safePath, 'utf8');
592
-
593
- // Parse CSV with hooks and caching
594
- return csvToJson(csvContent, options);
595
- } catch (error) {
596
- // Re-throw parsing errors as-is
597
- if (error instanceof ParsingError || error instanceof ValidationError || error instanceof LimitError) {
598
- throw error;
599
- }
600
-
601
- // Wrap file system errors
602
- if (error.code === 'ENOENT') {
603
- throw new FileSystemError(`File not found: ${safePath}`, error);
604
- }
605
- if (error.code === 'EACCES') {
606
- throw new FileSystemError(`Permission denied: ${safePath}`, error);
607
- }
608
- if (error.code === 'EISDIR') {
609
- throw new FileSystemError(`Path is a directory: ${safePath}`, error);
610
- }
611
-
612
- throw new FileSystemError(`Failed to read CSV file: ${error.message}`, error);
613
- }
614
- }
615
-
616
- /**
617
- * Synchronously reads CSV file and converts it to JSON array with hooks and caching support
618
- *
619
- * @param {string} filePath - Path to CSV file
620
- * @param {Object} [options] - Configuration options (same as csvToJson)
621
- * @returns {Array<Object>} JSON array
622
- */
623
- function readCsvAsJsonSync(filePath, options = {}) {
624
- const fs = require('fs');
625
-
626
- // Validate file path
627
- const safePath = validateCsvFilePath(filePath);
628
-
629
- try {
630
- // Read file
631
- const csvContent = fs.readFileSync(safePath, 'utf8');
632
-
633
- // Parse CSV with hooks and caching
634
- return csvToJson(csvContent, options);
635
- } catch (error) {
636
- // Re-throw parsing errors as-is
637
- if (error instanceof ParsingError || error instanceof ValidationError || error instanceof LimitError) {
638
- throw error;
639
- }
640
-
641
- // Wrap file system errors
642
- if (error.code === 'ENOENT') {
643
- throw new FileSystemError(`File not found: ${safePath}`, error);
644
- }
645
- if (error.code === 'EACCES') {
646
- throw new FileSystemError(`Permission denied: ${safePath}`, error);
647
- }
648
- if (error.code === 'EISDIR') {
649
- throw new FileSystemError(`Path is a directory: ${safePath}`, error);
650
- }
651
-
652
- throw new FileSystemError(`Failed to read CSV file: ${error.message}`, error);
653
- }
654
- }
655
-
656
- /**
657
- * Creates a new TransformHooks instance
658
- * @returns {TransformHooks} New TransformHooks instance
659
- */
660
- function createTransformHooks() {
661
- return new TransformHooks();
662
- }
663
-
664
- /**
665
- * Creates a new DelimiterCache instance
666
- * @param {number} maxSize - Maximum cache size (default: 100)
667
- * @returns {DelimiterCache} New DelimiterCache instance
668
- */
669
- /* istanbul ignore next */
670
- function createDelimiterCache(maxSize = 100) {
671
- return new DelimiterCache(maxSize);
672
- }
673
-
674
- /**
675
- * Gets statistics from the global delimiter cache
676
- * @returns {Object} Cache statistics
677
- */
678
- /* istanbul ignore next */
679
- function getDelimiterCacheStats() {
680
- return globalDelimiterCache.getStats();
681
- }
682
-
683
- /**
684
- * Clears the global delimiter cache
685
- */
686
- /* istanbul ignore next */
687
- function clearDelimiterCache() {
688
- globalDelimiterCache.clear();
689
- }
690
-
691
- // Export the functions
692
- module.exports = {
693
- csvToJson,
694
- csvToJsonIterator,
695
- readCsvAsJson,
696
- readCsvAsJsonSync,
697
- autoDetectDelimiter,
698
- createTransformHooks,
699
- createDelimiterCache,
700
- getDelimiterCacheStats,
701
- clearDelimiterCache,
702
- TransformHooks,
703
- DelimiterCache,
704
- predefinedHooks
705
- };
706
-
707
- // For ES6 module compatibility
708
- /* istanbul ignore next */
709
- if (typeof module !== 'undefined' && module.exports) {
710
- module.exports.default = csvToJson;
711
- }