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
@@ -1,393 +0,0 @@
1
- import {
2
- ValidationError,
3
- ConfigurationError,
4
- LimitError
5
- } from './errors-browser.js';
6
- import { csvToJsonIterator } from './csv-to-json-browser.js';
7
-
8
- const DEFAULT_MAX_CHUNK_SIZE = 64 * 1024;
9
-
10
- function isReadableStream(value) {
11
- return value && typeof value.getReader === 'function';
12
- }
13
-
14
- function isAsyncIterable(value) {
15
- return value && typeof value[Symbol.asyncIterator] === 'function';
16
- }
17
-
18
- function isIterable(value) {
19
- return value && typeof value[Symbol.iterator] === 'function';
20
- }
21
-
22
- function createReadableStreamFromIterator(iterator) {
23
- return new ReadableStream({
24
- async pull(controller) {
25
- try {
26
- const { value, done } = await iterator.next();
27
- if (done) {
28
- controller.close();
29
- return;
30
- }
31
- controller.enqueue(value);
32
- } catch (error) {
33
- controller.error(error);
34
- }
35
- },
36
- cancel() {
37
- if (iterator.return) {
38
- iterator.return();
39
- }
40
- }
41
- });
42
- }
43
-
44
- function detectInputFormat(input, options) {
45
- if (options && options.inputFormat) {
46
- return options.inputFormat;
47
- }
48
-
49
- if (typeof input === 'string') {
50
- const trimmed = input.trim();
51
- if (trimmed.startsWith('[')) {
52
- return 'json-array';
53
- }
54
- if (trimmed.includes('\n')) {
55
- return 'ndjson';
56
- }
57
- return 'json-array';
58
- }
59
-
60
- if (input instanceof Blob || isReadableStream(input)) {
61
- return 'ndjson';
62
- }
63
-
64
- return 'json-array';
65
- }
66
-
67
- async function* parseNdjsonText(text) {
68
- const lines = text.split(/\r?\n/);
69
- for (const line of lines) {
70
- const trimmed = line.trim();
71
- if (!trimmed) {
72
- continue;
73
- }
74
- yield JSON.parse(trimmed);
75
- }
76
- }
77
-
78
- async function* parseNdjsonStream(stream) {
79
- const reader = stream.getReader();
80
- const decoder = new TextDecoder('utf-8');
81
- let buffer = '';
82
-
83
- while (true) {
84
- const { value, done } = await reader.read();
85
- if (done) {
86
- break;
87
- }
88
-
89
- buffer += decoder.decode(value, { stream: true });
90
- const lines = buffer.split(/\r?\n/);
91
- buffer = lines.pop() || '';
92
-
93
- for (const line of lines) {
94
- const trimmed = line.trim();
95
- if (!trimmed) {
96
- continue;
97
- }
98
- yield JSON.parse(trimmed);
99
- }
100
- }
101
-
102
- if (buffer.trim()) {
103
- yield JSON.parse(buffer.trim());
104
- }
105
- }
106
-
107
- async function* normalizeJsonInput(input, options = {}) {
108
- const format = detectInputFormat(input, options);
109
-
110
- if (Array.isArray(input)) {
111
- for (const item of input) {
112
- yield item;
113
- }
114
- return;
115
- }
116
-
117
- if (isAsyncIterable(input)) {
118
- for await (const item of input) {
119
- yield item;
120
- }
121
- return;
122
- }
123
-
124
- if (isIterable(input)) {
125
- for (const item of input) {
126
- yield item;
127
- }
128
- return;
129
- }
130
-
131
- if (typeof input === 'string') {
132
- if (format === 'ndjson') {
133
- yield* parseNdjsonText(input);
134
- return;
135
- }
136
-
137
- const parsed = JSON.parse(input);
138
- if (Array.isArray(parsed)) {
139
- for (const item of parsed) {
140
- yield item;
141
- }
142
- return;
143
- }
144
- yield parsed;
145
- return;
146
- }
147
-
148
- if (input instanceof Blob) {
149
- if (format === 'ndjson') {
150
- yield* parseNdjsonStream(input.stream());
151
- return;
152
- }
153
-
154
- const text = await input.text();
155
- const parsed = JSON.parse(text);
156
- if (Array.isArray(parsed)) {
157
- for (const item of parsed) {
158
- yield item;
159
- }
160
- return;
161
- }
162
- yield parsed;
163
- return;
164
- }
165
-
166
- if (isReadableStream(input)) {
167
- if (format !== 'ndjson') {
168
- throw new ValidationError('ReadableStream input requires inputFormat="ndjson"');
169
- }
170
- yield* parseNdjsonStream(input);
171
- return;
172
- }
173
-
174
- throw new ValidationError('Input must be an array, iterable, string, Blob, or ReadableStream');
175
- }
176
-
177
- function validateStreamOptions(options) {
178
- if (options && typeof options !== 'object') {
179
- throw new ConfigurationError('Options must be an object');
180
- }
181
-
182
- if (options?.delimiter && typeof options.delimiter !== 'string') {
183
- throw new ConfigurationError('Delimiter must be a string');
184
- }
185
-
186
- if (options?.delimiter && options.delimiter.length !== 1) {
187
- throw new ConfigurationError('Delimiter must be a single character');
188
- }
189
-
190
- if (options?.renameMap && typeof options.renameMap !== 'object') {
191
- throw new ConfigurationError('renameMap must be an object');
192
- }
193
-
194
- if (options?.maxRecords !== undefined) {
195
- if (typeof options.maxRecords !== 'number' || options.maxRecords <= 0) {
196
- throw new ConfigurationError('maxRecords must be a positive number');
197
- }
198
- }
199
- }
200
-
201
- function escapeCsvValue(value, options) {
202
- const {
203
- delimiter,
204
- preventCsvInjection = true,
205
- rfc4180Compliant = true
206
- } = options;
207
-
208
- if (value === null || value === undefined || value === '') {
209
- return '';
210
- }
211
-
212
- const stringValue = String(value);
213
- let escapedValue = stringValue;
214
- if (preventCsvInjection && /^[=+\-@]/.test(stringValue)) {
215
- escapedValue = "'" + stringValue;
216
- }
217
-
218
- const needsQuoting = rfc4180Compliant
219
- ? (escapedValue.includes(delimiter) ||
220
- escapedValue.includes('"') ||
221
- escapedValue.includes('\n') ||
222
- escapedValue.includes('\r'))
223
- : (escapedValue.includes(delimiter) ||
224
- escapedValue.includes('"') ||
225
- escapedValue.includes('\n') ||
226
- escapedValue.includes('\r'));
227
-
228
- if (needsQuoting) {
229
- return `"${escapedValue.replace(/"/g, '""')}"`;
230
- }
231
-
232
- return escapedValue;
233
- }
234
-
235
- function buildHeaderState(keys, options) {
236
- const renameMap = options.renameMap || {};
237
- const template = options.template || {};
238
- const originalKeys = Array.isArray(options.headers) ? options.headers : keys;
239
- const headers = originalKeys.map((key) => renameMap[key] || key);
240
-
241
- const reverseRenameMap = {};
242
- originalKeys.forEach((key, index) => {
243
- reverseRenameMap[headers[index]] = key;
244
- });
245
-
246
- let finalHeaders = headers;
247
- if (Object.keys(template).length > 0) {
248
- const templateHeaders = Object.keys(template).map(key => renameMap[key] || key);
249
- const extraHeaders = headers.filter(h => !templateHeaders.includes(h));
250
- finalHeaders = [...templateHeaders, ...extraHeaders];
251
- }
252
-
253
- return {
254
- headers: finalHeaders,
255
- reverseRenameMap
256
- };
257
- }
258
-
259
- async function* jsonToCsvChunkIterator(input, options = {}) {
260
- validateStreamOptions(options);
261
-
262
- const opts = options && typeof options === 'object' ? options : {};
263
- const {
264
- delimiter = ';',
265
- includeHeaders = true,
266
- maxRecords,
267
- maxChunkSize = DEFAULT_MAX_CHUNK_SIZE,
268
- headerMode
269
- } = opts;
270
-
271
- let headerState = null;
272
- let buffer = '';
273
- let recordCount = 0;
274
- const lineEnding = opts.rfc4180Compliant === false ? '\n' : '\r\n';
275
-
276
- if (Array.isArray(input) && !opts.headers && (!headerMode || headerMode === 'all')) {
277
- const allKeys = new Set();
278
- for (const item of input) {
279
- if (!item || typeof item !== 'object') {
280
- continue;
281
- }
282
- Object.keys(item).forEach((key) => allKeys.add(key));
283
- }
284
- headerState = buildHeaderState(Array.from(allKeys), opts);
285
- if (includeHeaders && headerState.headers.length > 0) {
286
- buffer += headerState.headers.join(delimiter) + lineEnding;
287
- }
288
- } else if (Array.isArray(opts.headers)) {
289
- headerState = buildHeaderState(opts.headers, opts);
290
- if (includeHeaders && headerState.headers.length > 0) {
291
- buffer += headerState.headers.join(delimiter) + lineEnding;
292
- }
293
- }
294
-
295
- for await (const item of normalizeJsonInput(input, opts)) {
296
- if (!item || typeof item !== 'object') {
297
- continue;
298
- }
299
-
300
- if (!headerState) {
301
- headerState = buildHeaderState(Object.keys(item), opts);
302
- if (includeHeaders && headerState.headers.length > 0) {
303
- buffer += headerState.headers.join(delimiter) + lineEnding;
304
- }
305
- }
306
-
307
- recordCount += 1;
308
- if (maxRecords && recordCount > maxRecords) {
309
- throw new LimitError(
310
- `Data size exceeds maximum limit of ${maxRecords} records`,
311
- maxRecords,
312
- recordCount
313
- );
314
- }
315
-
316
- const row = headerState.headers.map((header) => {
317
- const originalKey = headerState.reverseRenameMap[header] || header;
318
- return escapeCsvValue(item[originalKey], {
319
- delimiter,
320
- preventCsvInjection: opts.preventCsvInjection !== false,
321
- rfc4180Compliant: opts.rfc4180Compliant !== false
322
- });
323
- }).join(delimiter);
324
-
325
- buffer += row + lineEnding;
326
-
327
- if (buffer.length >= maxChunkSize) {
328
- yield buffer;
329
- buffer = '';
330
- }
331
- }
332
-
333
- if (buffer.length > 0) {
334
- yield buffer;
335
- }
336
- }
337
-
338
- async function* jsonToNdjsonChunkIterator(input, options = {}) {
339
- validateStreamOptions(options);
340
- for await (const item of normalizeJsonInput(input, options)) {
341
- if (item === undefined) {
342
- continue;
343
- }
344
- yield JSON.stringify(item) + '\n';
345
- }
346
- }
347
-
348
- async function* csvToJsonChunkIterator(input, options = {}) {
349
- const outputFormat = options.outputFormat || 'ndjson';
350
- const asArray = outputFormat === 'json-array' || outputFormat === 'array' || outputFormat === 'json';
351
- let first = true;
352
-
353
- if (asArray) {
354
- yield '[';
355
- }
356
-
357
- for await (const row of csvToJsonIterator(input, options)) {
358
- const payload = JSON.stringify(row);
359
- if (asArray) {
360
- yield (first ? '' : ',') + payload;
361
- } else {
362
- yield payload + '\n';
363
- }
364
- first = false;
365
- }
366
-
367
- if (asArray) {
368
- yield ']';
369
- }
370
- }
371
-
372
- export function jsonToCsvStream(input, options = {}) {
373
- const iterator = jsonToCsvChunkIterator(input, options);
374
- return createReadableStreamFromIterator(iterator);
375
- }
376
-
377
- export function jsonToNdjsonStream(input, options = {}) {
378
- const iterator = jsonToNdjsonChunkIterator(input, options);
379
- return createReadableStreamFromIterator(iterator);
380
- }
381
-
382
- export function csvToJsonStream(input, options = {}) {
383
- const iterator = csvToJsonChunkIterator(input, options);
384
- return createReadableStreamFromIterator(iterator);
385
- }
386
-
387
- if (typeof module !== 'undefined' && module.exports) {
388
- module.exports = {
389
- jsonToCsvStream,
390
- jsonToNdjsonStream,
391
- csvToJsonStream
392
- };
393
- }
@@ -1,186 +0,0 @@
1
- /**
2
- * Кэширование результатов авто-детектирования разделителя
3
- * Использует WeakMap и LRU кэш для оптимизации производительности
4
- *
5
- * @version 1.0.0
6
- * @date 2026-01-23
7
- */
8
-
9
- class DelimiterCache {
10
- constructor(maxSize = 100) {
11
- this.weakMap = new WeakMap();
12
- this.lruCache = new Map();
13
- this.maxSize = maxSize;
14
- this.stats = {
15
- hits: 0,
16
- misses: 0,
17
- evictions: 0,
18
- size: 0
19
- };
20
- }
21
-
22
- /**
23
- * Генерирует ключ кэша на основе строки и кандидатов
24
- * @private
25
- */
26
- _generateKey(csv, candidates) {
27
- // Используем хэш первых 1000 символов для производительности
28
- const sample = csv.substring(0, Math.min(1000, csv.length));
29
- const candidatesKey = candidates.join(',');
30
- return `${this._hashString(sample)}:${candidatesKey}`;
31
- }
32
-
33
- /**
34
- * Простая хэш-функция для строк
35
- * @private
36
- */
37
- _hashString(str) {
38
- let hash = 0;
39
- for (let i = 0; i < str.length; i++) {
40
- const char = str.charCodeAt(i);
41
- hash = ((hash << 5) - hash) + char;
42
- hash = hash & hash; // Convert to 32bit integer
43
- }
44
- return hash.toString(36);
45
- }
46
-
47
- /**
48
- * Получает значение из кэша
49
- * @param {string} csv - CSV строка
50
- * @param {Array} candidates - Кандидаты разделителей
51
- * @returns {string|null} Кэшированный разделитель или null
52
- */
53
- get(csv, candidates) {
54
- const key = this._generateKey(csv, candidates);
55
-
56
- // Проверяем LRU кэш
57
- if (this.lruCache.has(key)) {
58
- // Обновляем позицию в LRU
59
- const value = this.lruCache.get(key);
60
- this.lruCache.delete(key);
61
- this.lruCache.set(key, value);
62
- this.stats.hits++;
63
- return value;
64
- }
65
-
66
- this.stats.misses++;
67
- return null;
68
- }
69
-
70
- /**
71
- * Сохраняет значение в кэш
72
- * @param {string} csv - CSV строка
73
- * @param {Array} candidates - Кандидаты разделителей
74
- * @param {string} delimiter - Найденный разделитель
75
- */
76
- set(csv, candidates, delimiter) {
77
- const key = this._generateKey(csv, candidates);
78
-
79
- // Проверяем необходимость вытеснения из LRU кэша
80
- if (this.lruCache.size >= this.maxSize) {
81
- // Удаляем самый старый элемент (первый в Map)
82
- const firstKey = this.lruCache.keys().next().value;
83
- this.lruCache.delete(firstKey);
84
- this.stats.evictions++;
85
- }
86
-
87
- // Сохраняем в LRU кэш
88
- this.lruCache.set(key, delimiter);
89
- this.stats.size = this.lruCache.size;
90
-
91
- // Также сохраняем в WeakMap если csv является объектом
92
- if (typeof csv === 'object' && csv !== null) {
93
- this.weakMap.set(csv, { candidates, delimiter });
94
- }
95
- }
96
-
97
- /**
98
- * Очищает кэш
99
- */
100
- clear() {
101
- this.lruCache.clear();
102
- this.weakMap = new WeakMap();
103
- this.stats = {
104
- hits: 0,
105
- misses: 0,
106
- evictions: 0,
107
- size: 0
108
- };
109
- }
110
-
111
- /**
112
- * Возвращает статистику кэша
113
- * @returns {Object} Статистика
114
- */
115
- getStats() {
116
- const total = this.stats.hits + this.stats.misses;
117
- return {
118
- ...this.stats,
119
- hitRate: total > 0 ? (this.stats.hits / total) * 100 : 0,
120
- totalRequests: total
121
- };
122
- }
123
-
124
- /**
125
- * Оптимизированная версия autoDetectDelimiter с кэшированием
126
- * @param {string} csv - CSV строка
127
- * @param {Array} candidates - Кандидаты разделителей
128
- * @param {DelimiterCache} cache - Экземпляр кэша (опционально)
129
- * @returns {string} Найденный разделитель
130
- */
131
- static autoDetectDelimiter(csv, candidates = [';', ',', '\t', '|'], cache = null) {
132
- if (!csv || typeof csv !== 'string') {
133
- return ';';
134
- }
135
-
136
- // Проверяем кэш если он предоставлен
137
- if (cache) {
138
- const cached = cache.get(csv, candidates);
139
- if (cached !== null) {
140
- return cached;
141
- }
142
- }
143
-
144
- const lines = csv.split('\n').filter(line => line.trim().length > 0);
145
-
146
- if (lines.length === 0) {
147
- return ';';
148
- }
149
-
150
- // Используем первую непустую строку для детектирования
151
- const firstLine = lines[0];
152
-
153
- const counts = {};
154
- candidates.forEach(delim => {
155
- const escapedDelim = delim.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
156
- const regex = new RegExp(escapedDelim, 'g');
157
- const matches = firstLine.match(regex);
158
- counts[delim] = matches ? matches.length : 0;
159
- });
160
-
161
- // Находим разделитель с максимальным количеством
162
- let maxCount = -1;
163
- let detectedDelimiter = ';';
164
-
165
- for (const [delim, count] of Object.entries(counts)) {
166
- if (count > maxCount) {
167
- maxCount = count;
168
- detectedDelimiter = delim;
169
- }
170
- }
171
-
172
- // Если разделитель не найден или есть ничья, возвращаем стандартный
173
- if (maxCount === 0) {
174
- detectedDelimiter = ';';
175
- }
176
-
177
- // Сохраняем в кэш если он предоставлен
178
- if (cache) {
179
- cache.set(csv, candidates, detectedDelimiter);
180
- }
181
-
182
- return detectedDelimiter;
183
- }
184
- }
185
-
186
- module.exports = DelimiterCache;