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,192 @@
1
+ // Браузерный entry point для jtcsv
2
+ // Экспортирует все функции с поддержкой браузера
3
+
4
+ import * as jsonToCsvBrowser from './json-to-csv-browser';
5
+ import * as csvToJsonBrowser from './csv-to-json-browser';
6
+ import {
7
+ downloadAsCsv,
8
+ parseCsvFile,
9
+ parseCsvFileStream,
10
+ jsonToCsvStream,
11
+ jsonToNdjsonStream,
12
+ csvToJsonStream
13
+ } from './browser-functions';
14
+ import { createWorkerPool, parseCSVWithWorker } from './workers/worker-pool';
15
+ import {
16
+ ValidationError,
17
+ SecurityError,
18
+ FileSystemError,
19
+ ParsingError,
20
+ LimitError,
21
+ ConfigurationError,
22
+ ERROR_CODES
23
+ } from './errors-browser';
24
+
25
+ import type { JsonToCsvOptions, CsvToJsonOptions } from '../types';
26
+
27
+ const { jsonToCsv, preprocessData, deepUnwrap } = jsonToCsvBrowser as any;
28
+ const { csvToJson, csvToJsonIterator, autoDetectDelimiter } = csvToJsonBrowser as any;
29
+
30
+ /**
31
+ * Опции для ленивой инициализации Worker Pool
32
+ */
33
+ interface WorkerPoolOptions {
34
+ size?: number;
35
+ timeout?: number;
36
+ onError?: (error: Error) => void;
37
+ }
38
+
39
+ /**
40
+ * Ленивая инициализация Worker Pool
41
+ */
42
+ async function createWorkerPoolLazy(options: any = {}): Promise<any> {
43
+ const mod = await import('./workers/worker-pool');
44
+ return mod.createWorkerPool(options);
45
+ }
46
+
47
+ /**
48
+ * Ленивый парсинг CSV с использованием Worker
49
+ */
50
+ async function parseCSVWithWorkerLazy(
51
+ csvInput: string | File,
52
+ options: CsvToJsonOptions = {},
53
+ onProgress?: (progress: number) => void
54
+ ): Promise<any[]> {
55
+ const mod = await import('./workers/worker-pool');
56
+ return mod.parseCSVWithWorker(csvInput, options, onProgress);
57
+ }
58
+
59
+ /**
60
+ * Асинхронная версия jsonToCsv
61
+ */
62
+ async function jsonToCsvAsync(data: any, options: JsonToCsvOptions = {}): Promise<string> {
63
+ return jsonToCsv(data, options);
64
+ }
65
+
66
+ /**
67
+ * Асинхронная версия csvToJson
68
+ */
69
+ async function csvToJsonAsync(csv: string, options: CsvToJsonOptions = {}): Promise<any[]> {
70
+ return csvToJson(csv, options);
71
+ }
72
+
73
+ /**
74
+ * Асинхронная версия parseCsvFile
75
+ */
76
+ async function parseCsvFileAsync(file: File, options: CsvToJsonOptions = {}): Promise<any[]> {
77
+ return parseCsvFile(file, options);
78
+ }
79
+
80
+ /**
81
+ * Асинхронная версия autoDetectDelimiter
82
+ */
83
+ async function autoDetectDelimiterAsync(csv: string): Promise<string> {
84
+ return autoDetectDelimiter(csv);
85
+ }
86
+
87
+ /**
88
+ * Асинхронная версия downloadAsCsv
89
+ */
90
+ async function downloadAsCsvAsync(
91
+ data: any,
92
+ filename: string = 'export.csv',
93
+ options: JsonToCsvOptions = {}
94
+ ): Promise<void> {
95
+ return downloadAsCsv(data, filename, options);
96
+ }
97
+
98
+ // Основной экспорт
99
+ const jtcsv = {
100
+ // JSON to CSV функции
101
+ jsonToCsv,
102
+ preprocessData,
103
+ downloadAsCsv,
104
+ deepUnwrap,
105
+
106
+ // CSV to JSON функции
107
+ csvToJson,
108
+ csvToJsonIterator,
109
+ parseCsvFile,
110
+ parseCsvFileStream,
111
+ jsonToCsvStream,
112
+ jsonToNdjsonStream,
113
+ csvToJsonStream,
114
+ autoDetectDelimiter,
115
+
116
+ // Web Workers функции
117
+ createWorkerPool,
118
+ parseCSVWithWorker,
119
+ createWorkerPoolLazy,
120
+ parseCSVWithWorkerLazy,
121
+
122
+ // Асинхронные функции
123
+ jsonToCsvAsync,
124
+ csvToJsonAsync,
125
+ parseCsvFileAsync,
126
+ autoDetectDelimiterAsync,
127
+ downloadAsCsvAsync,
128
+
129
+ // Error classes
130
+ ValidationError,
131
+ SecurityError,
132
+ FileSystemError,
133
+ ParsingError,
134
+ LimitError,
135
+ ConfigurationError,
136
+ ERROR_CODES,
137
+
138
+ // Удобные алиасы
139
+ parse: csvToJson,
140
+ unparse: jsonToCsv,
141
+ parseAsync: csvToJsonAsync,
142
+ unparseAsync: jsonToCsvAsync,
143
+
144
+ // Версия
145
+ version: '2.0.0-browser'
146
+ };
147
+
148
+ // Экспорт для разных сред
149
+ if (typeof module !== 'undefined' && module.exports) {
150
+ // Node.js CommonJS
151
+ module.exports = jtcsv;
152
+ } else if (typeof define === 'function' && define.amd) {
153
+ // AMD
154
+ define([], () => jtcsv);
155
+ } else if (typeof window !== 'undefined') {
156
+ // Браузер (глобальная переменная)
157
+ (window as any).jtcsv = jtcsv;
158
+ }
159
+
160
+ export default jtcsv;
161
+ export {
162
+ jsonToCsv,
163
+ preprocessData,
164
+ downloadAsCsv,
165
+ deepUnwrap,
166
+ csvToJson,
167
+ csvToJsonIterator,
168
+ parseCsvFile,
169
+ parseCsvFileStream,
170
+ jsonToCsvStream,
171
+ jsonToNdjsonStream,
172
+ csvToJsonStream,
173
+ autoDetectDelimiter,
174
+ createWorkerPool,
175
+ parseCSVWithWorker,
176
+ createWorkerPoolLazy,
177
+ parseCSVWithWorkerLazy,
178
+ // Асинхронные функции
179
+ jsonToCsvAsync,
180
+ csvToJsonAsync,
181
+ parseCsvFileAsync,
182
+ autoDetectDelimiterAsync,
183
+ downloadAsCsvAsync,
184
+ // Error classes
185
+ ValidationError,
186
+ SecurityError,
187
+ FileSystemError,
188
+ ParsingError,
189
+ LimitError,
190
+ ConfigurationError,
191
+ ERROR_CODES
192
+ };
@@ -0,0 +1,3 @@
1
+ export function jsonToCsv(data: any, options?: any): string;
2
+ export function preprocessData(data: any[]): any[];
3
+ export function deepUnwrap(value: any, depth?: number, maxDepth?: number, visited?: Set<any>): any;
@@ -0,0 +1,338 @@
1
+ // Браузерная версия JSON to CSV конвертера
2
+ // Адаптирована для работы в браузере без Node.js API
3
+
4
+ import {
5
+ ValidationError,
6
+ ConfigurationError,
7
+ LimitError,
8
+ safeExecute
9
+ } from './errors-browser';
10
+
11
+ import type { JsonToCsvOptions } from '../types';
12
+
13
+ /**
14
+ * Валидация входных данных и опций
15
+ * @private
16
+ */
17
+ function validateInput(data: any[], options: JsonToCsvOptions): boolean {
18
+ // Validate data
19
+ if (!Array.isArray(data)) {
20
+ throw new ValidationError('Input data must be an array');
21
+ }
22
+
23
+ // Validate options
24
+ if (options && typeof options !== 'object') {
25
+ throw new ConfigurationError('Options must be an object');
26
+ }
27
+
28
+ // Validate delimiter
29
+ if (options?.delimiter && typeof options.delimiter !== 'string') {
30
+ throw new ConfigurationError('Delimiter must be a string');
31
+ }
32
+
33
+ if (options?.delimiter && options.delimiter.length !== 1) {
34
+ throw new ConfigurationError('Delimiter must be a single character');
35
+ }
36
+
37
+ // Validate renameMap
38
+ if (options?.renameMap && typeof options.renameMap !== 'object') {
39
+ throw new ConfigurationError('renameMap must be an object');
40
+ }
41
+
42
+ // Validate maxRecords
43
+ if (options && options.maxRecords !== undefined) {
44
+ if (typeof options.maxRecords !== 'number' || options.maxRecords <= 0) {
45
+ throw new ConfigurationError('maxRecords must be a positive number');
46
+ }
47
+ }
48
+
49
+ // Validate preventCsvInjection
50
+ if (options?.preventCsvInjection !== undefined && typeof options.preventCsvInjection !== 'boolean') {
51
+ throw new ConfigurationError('preventCsvInjection must be a boolean');
52
+ }
53
+
54
+ // Validate rfc4180Compliant
55
+ if (options?.rfc4180Compliant !== undefined && typeof options.rfc4180Compliant !== 'boolean') {
56
+ throw new ConfigurationError('rfc4180Compliant must be a boolean');
57
+ }
58
+
59
+ if (options?.normalizeQuotes !== undefined && typeof options.normalizeQuotes !== 'boolean') {
60
+ throw new ConfigurationError('normalizeQuotes must be a boolean');
61
+ }
62
+
63
+ return true;
64
+ }
65
+
66
+ /**
67
+ * Экранирование CSV значений для предотвращения инъекций
68
+ * @private
69
+ */
70
+ function escapeCsvValue(value: string, preventInjection: boolean = true): string {
71
+ if (value === null || value === undefined) {
72
+ return '';
73
+ }
74
+
75
+ const str = String(value);
76
+ const isPotentialFormula = (input: string): boolean => {
77
+ let idx = 0;
78
+ while (idx < input.length) {
79
+ const code = input.charCodeAt(idx);
80
+ if (code === 32 || code === 9 || code === 10 || code === 13 || code === 0xfeff) {
81
+ idx++;
82
+ continue;
83
+ }
84
+ break;
85
+ }
86
+ if (idx < input.length && (input[idx] === '"' || input[idx] === "'")) {
87
+ idx++;
88
+ while (idx < input.length) {
89
+ const code = input.charCodeAt(idx);
90
+ if (code === 32 || code === 9) {
91
+ idx++;
92
+ continue;
93
+ }
94
+ break;
95
+ }
96
+ }
97
+ if (idx >= input.length) {
98
+ return false;
99
+ }
100
+ const char = input[idx];
101
+ return char === '=' || char === '+' || char === '-' || char === '@';
102
+ };
103
+
104
+ // Экранирование формул для предотвращения CSV инъекций
105
+ if (preventInjection && isPotentialFormula(str)) {
106
+ return "'" + str;
107
+ }
108
+
109
+ // Экранирование кавычек и переносов строк
110
+ if (str.includes('"') || str.includes('\n') || str.includes('\r') || str.includes(',')) {
111
+ return '"' + str.replace(/"/g, '""') + '"';
112
+ }
113
+
114
+ return str;
115
+ }
116
+
117
+ function normalizeQuotesInField(value: string): string {
118
+ // Не нормализуем кавычки в JSON-строках - это ломает структуру JSON
119
+ // Проверяем, выглядит ли значение как JSON (объект или массив)
120
+ if ((value.startsWith('{') && value.endsWith('}')) ||
121
+ (value.startsWith('[') && value.endsWith(']'))) {
122
+ return value; // Возвращаем как есть для JSON
123
+ }
124
+
125
+ let normalized = value.replace(/"{2,}/g, '"');
126
+ // Убираем правило, которое ломает JSON: не заменяем "," на ","
127
+ // normalized = normalized.replace(/"\s*,\s*"/g, ',');
128
+ normalized = normalized.replace(/"\n/g, '\n').replace(/\n"/g, '\n');
129
+ if (normalized.length >= 2 && normalized.startsWith('"') && normalized.endsWith('"')) {
130
+ normalized = normalized.slice(1, -1);
131
+ }
132
+ return normalized;
133
+ }
134
+
135
+ function normalizePhoneValue(value: string): string {
136
+ const trimmed = value.trim();
137
+ if (trimmed === '') {
138
+ return trimmed;
139
+ }
140
+ return trimmed.replace(/["'\\]/g, '');
141
+ }
142
+
143
+ function normalizeValueForCsv(value: any, key: string | undefined, normalizeQuotes: boolean): any {
144
+ if (!normalizeQuotes || typeof value !== 'string') {
145
+ return value;
146
+ }
147
+ const base = normalizeQuotesInField(value);
148
+ if (!key) {
149
+ return base;
150
+ }
151
+ const phoneKeys = new Set(['phone', 'phonenumber', 'phone_number', 'tel', 'telephone']);
152
+ if (phoneKeys.has(String(key).toLowerCase())) {
153
+ return normalizePhoneValue(base);
154
+ }
155
+ return base;
156
+ }
157
+
158
+ /**
159
+ * Извлечение всех уникальных ключей из массива объектов
160
+ * @private
161
+ */
162
+ function extractAllKeys(data: any[]): string[] {
163
+ const keys = new Set<string>();
164
+
165
+ for (const item of data) {
166
+ if (item && typeof item === 'object') {
167
+ Object.keys(item).forEach(key => keys.add(key));
168
+ }
169
+ }
170
+
171
+ return Array.from(keys);
172
+ }
173
+
174
+ /**
175
+ * Конвертация массива объектов в CSV строку
176
+ *
177
+ * @param data - Массив объектов для конвертации
178
+ * @param options - Опции конвертации
179
+ * @returns CSV строка
180
+ */
181
+ export function jsonToCsv(data: any[], options: JsonToCsvOptions = {}): string {
182
+ return safeExecute(() => {
183
+ validateInput(data, options);
184
+
185
+ if (data.length === 0) {
186
+ return '';
187
+ }
188
+
189
+ // Настройки по умолчанию
190
+ const delimiter = options.delimiter || ';';
191
+ const includeHeaders = options.includeHeaders !== false;
192
+ const maxRecords = options.maxRecords || data.length;
193
+ const preventInjection = options.preventCsvInjection !== false;
194
+ const rfc4180Compliant = options.rfc4180Compliant !== false;
195
+ const normalizeQuotes = options.normalizeQuotes !== false;
196
+
197
+ // Ограничение количества записей
198
+ const limitedData = data.slice(0, maxRecords);
199
+
200
+ // Извлечение всех ключей
201
+ const allKeys = extractAllKeys(limitedData);
202
+
203
+ // Применение renameMap если есть
204
+ const renameMap = options.renameMap || {};
205
+ const finalKeys = allKeys.map(key => renameMap[key] || key);
206
+
207
+ // Создание CSV строки
208
+ const lines: string[] = [];
209
+
210
+ // Заголовки
211
+ if (includeHeaders) {
212
+ const headerLine = finalKeys.map(key => escapeCsvValue(key, preventInjection)).join(delimiter);
213
+ lines.push(headerLine);
214
+ }
215
+
216
+ // Данные
217
+ for (const item of limitedData) {
218
+ const rowValues = allKeys.map(key => {
219
+ const value = item?.[key];
220
+ const normalized = normalizeValueForCsv(value, key, normalizeQuotes);
221
+ return escapeCsvValue(normalized, preventInjection);
222
+ });
223
+
224
+ lines.push(rowValues.join(delimiter));
225
+ }
226
+
227
+ // RFC 4180 compliance: CRLF line endings
228
+ if (rfc4180Compliant) {
229
+ return lines.join('\r\n');
230
+ }
231
+
232
+ return lines.join('\n');
233
+ });
234
+ }
235
+
236
+ /**
237
+ * Асинхронная версия jsonToCsv
238
+ */
239
+ export async function jsonToCsvAsync(data: any[], options: JsonToCsvOptions = {}): Promise<string> {
240
+ return jsonToCsv(data, options);
241
+ }
242
+
243
+ /**
244
+ * Создает итератор для потоковой конвертации JSON в CSV
245
+ *
246
+ * @param data - Массив объектов или async итератор
247
+ * @param options - Опции конвертации
248
+ * @returns AsyncIterator с CSV чанками
249
+ */
250
+ export async function* jsonToCsvIterator(data: any[] | AsyncIterable<any>, options: JsonToCsvOptions = {}): AsyncGenerator<string> {
251
+ validateInput(Array.isArray(data) ? data : [], options);
252
+
253
+ const delimiter = options.delimiter || ';';
254
+ const includeHeaders = options.includeHeaders !== false;
255
+ const preventInjection = options.preventCsvInjection !== false;
256
+ const rfc4180Compliant = options.rfc4180Compliant !== false;
257
+ const normalizeQuotes = options.normalizeQuotes !== false;
258
+
259
+ let isFirstChunk = true;
260
+ let allKeys: string[] = [];
261
+ let renameMap: Record<string, string> = {};
262
+
263
+ // Если данные - массив, обрабатываем как массив
264
+ if (Array.isArray(data)) {
265
+ if (data.length === 0) {
266
+ return;
267
+ }
268
+
269
+ allKeys = extractAllKeys(data);
270
+ renameMap = options.renameMap || {};
271
+ const finalKeys = allKeys.map(key => renameMap[key] || key);
272
+
273
+ // Заголовки
274
+ if (includeHeaders) {
275
+ const headerLine = finalKeys.map(key => escapeCsvValue(key, preventInjection)).join(delimiter);
276
+ yield headerLine + (rfc4180Compliant ? '\r\n' : '\n');
277
+ }
278
+
279
+ // Данные
280
+ for (const item of data) {
281
+ const rowValues = allKeys.map(key => {
282
+ const value = item?.[key];
283
+ const normalized = normalizeValueForCsv(value, key, normalizeQuotes);
284
+ return escapeCsvValue(normalized, preventInjection);
285
+ });
286
+
287
+ yield rowValues.join(delimiter) + (rfc4180Compliant ? '\r\n' : '\n');
288
+ }
289
+ } else {
290
+ // Для async итератора нужна другая логика
291
+ throw new ValidationError('Async iterators not yet implemented in browser version');
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Асинхронная версия jsonToCsvIterator (псевдоним)
297
+ */
298
+ export const jsonToCsvIteratorAsync = jsonToCsvIterator;
299
+
300
+ /**
301
+ * Безопасная конвертация с обработкой ошибок
302
+ *
303
+ * @param data - Массив объектов
304
+ * @param options - Опции конвертации
305
+ * @returns CSV строка или null при ошибке
306
+ */
307
+ export function jsonToCsvSafe(data: any[], options: JsonToCsvOptions = {}): string | null {
308
+ try {
309
+ return jsonToCsv(data, options);
310
+ } catch (error) {
311
+ console.error('JSON to CSV conversion error:', error);
312
+ return null;
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Асинхронная версия jsonToCsvSafe
318
+ */
319
+ export async function jsonToCsvSafeAsync(data: any[], options: JsonToCsvOptions = {}): Promise<string | null> {
320
+ try {
321
+ return await jsonToCsvAsync(data, options);
322
+ } catch (error) {
323
+ console.error('JSON to CSV conversion error:', error);
324
+ return null;
325
+ }
326
+ }
327
+
328
+ // Экспорт для Node.js совместимости
329
+ if (typeof module !== 'undefined' && module.exports) {
330
+ module.exports = {
331
+ jsonToCsv,
332
+ jsonToCsvAsync,
333
+ jsonToCsvIterator,
334
+ jsonToCsvIteratorAsync,
335
+ jsonToCsvSafe,
336
+ jsonToCsvSafeAsync
337
+ };
338
+ }