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