jtcsv 2.2.3 → 2.2.6

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jtcsv",
3
- "version": "2.2.3",
3
+ "version": "2.2.6",
4
4
  "description": "Complete JSON<->CSV and CSV<->JSON converter for Node.js and Browser with streaming, security, Web Workers, TypeScript support, and optional ecosystem (zero-deps core)",
5
5
  "main": "index.js",
6
6
  "browser": "dist/jtcsv.umd.js",
@@ -183,7 +183,7 @@
183
183
  },
184
184
  "homepage": "https://github.com/Linol-Hamelton/jtcsv#readme",
185
185
  "engines": {
186
- "node": ">=18.0.0"
186
+ "node": ">=12.0.0"
187
187
  },
188
188
  "files": [
189
189
  "index.js",
@@ -200,25 +200,28 @@
200
200
  "examples/",
201
201
  "plugins/"
202
202
  ],
203
- "dependencies": {
204
- "glob": "10.5.0"
203
+ "dependencies": {},
204
+ "optionalDependencies": {
205
+ "glob": "^11.0.0"
205
206
  },
206
207
  "devDependencies": {
207
- "@babel/core": "^7.23.0",
208
- "@babel/preset-env": "^7.22.0",
209
- "@rollup/plugin-babel": "^6.0.0",
210
- "@rollup/plugin-commonjs": "^25.0.0",
211
- "@rollup/plugin-node-resolve": "^15.0.0",
212
- "@rollup/plugin-terser": "^0.4.0",
213
- "@size-limit/preset-small-lib": "12.0.0",
208
+ "@babel/core": "^7.26.0",
209
+ "@babel/preset-env": "^7.26.0",
210
+ "@eslint/js": "^9.18.0",
211
+ "@rollup/plugin-babel": "^6.0.4",
212
+ "@rollup/plugin-commonjs": "^28.0.0",
213
+ "@rollup/plugin-node-resolve": "^16.0.0",
214
+ "@rollup/plugin-terser": "^0.4.4",
215
+ "@size-limit/preset-small-lib": "^11.1.0",
214
216
  "blessed": "^0.1.81",
215
- "blessed-contrib": "4.11.0",
216
- "eslint": "8.57.1",
217
- "jest": "^29.0.0",
218
- "jest-environment-jsdom": "30.2.0",
219
- "rollup": "^4.0.0",
220
- "size-limit": "12.0.0",
221
- "typedoc": "^0.25.0"
217
+ "blessed-contrib": "^4.11.0",
218
+ "eslint": "^9.18.0",
219
+ "globals": "^15.14.0",
220
+ "jest": "^29.7.0",
221
+ "jest-environment-jsdom": "^29.7.0",
222
+ "rollup": "^4.30.0",
223
+ "size-limit": "^11.1.0",
224
+ "typedoc": "^0.27.0"
222
225
  },
223
226
  "type": "commonjs",
224
227
  "size-limit": [
@@ -0,0 +1,408 @@
1
+ /**
2
+ * Node.js Runtime Optimizations
3
+ *
4
+ * Detects Node.js version and provides optimized implementations
5
+ * for modern runtimes while maintaining backward compatibility.
6
+ *
7
+ * Optimized for: Node 20, 22, 24
8
+ * Compatible with: Node 12+
9
+ */
10
+
11
+ // Parse Node.js version
12
+ const nodeVersion = process.versions?.node || '12.0.0';
13
+ const [major, minor] = nodeVersion.split('.').map(Number);
14
+
15
+ // Feature detection flags
16
+ const features = {
17
+ // Node 14.17+ / 16+
18
+ hasAbortController: typeof AbortController !== 'undefined',
19
+
20
+ // Node 15+
21
+ hasPromiseAny: typeof Promise.any === 'function',
22
+
23
+ // Node 16+
24
+ hasArrayAt: typeof Array.prototype.at === 'function',
25
+ hasObjectHasOwn: typeof Object.hasOwn === 'function',
26
+
27
+ // Node 17+
28
+ hasStructuredClone: typeof globalThis.structuredClone === 'function',
29
+
30
+ // Node 18+
31
+ hasFetch: typeof globalThis.fetch === 'function',
32
+
33
+ // Node 20+
34
+ hasWebStreams: typeof globalThis.ReadableStream !== 'undefined' && major >= 20,
35
+ hasArrayGroup: typeof Array.prototype.group === 'function',
36
+
37
+ // Node 21+
38
+ hasSetMethods: typeof Set.prototype.union === 'function',
39
+
40
+ // Node 22+
41
+ hasImportMeta: major >= 22,
42
+ hasExplicitResourceManagement: major >= 22,
43
+
44
+ // Version checks
45
+ isNode20Plus: major >= 20,
46
+ isNode22Plus: major >= 22,
47
+ isNode24Plus: major >= 24
48
+ };
49
+
50
+ /**
51
+ * Optimized Object.hasOwn polyfill for older Node versions
52
+ */
53
+ const hasOwn = features.hasObjectHasOwn
54
+ ? Object.hasOwn
55
+ : (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
56
+
57
+ /**
58
+ * Optimized deep clone function
59
+ * Uses structuredClone on Node 17+ for best performance
60
+ */
61
+ const deepClone = features.hasStructuredClone
62
+ ? (obj) => structuredClone(obj)
63
+ : (obj) => JSON.parse(JSON.stringify(obj));
64
+
65
+ /**
66
+ * Optimized array access with at() method
67
+ */
68
+ const arrayAt = features.hasArrayAt
69
+ ? (arr, index) => arr.at(index)
70
+ : (arr, index) => {
71
+ const len = arr.length;
72
+ const normalizedIndex = index < 0 ? len + index : index;
73
+ return normalizedIndex >= 0 && normalizedIndex < len ? arr[normalizedIndex] : undefined;
74
+ };
75
+
76
+ /**
77
+ * High-performance string builder for large CSV generation
78
+ * Uses different strategies based on Node version
79
+ */
80
+ class StringBuilderOptimized {
81
+ constructor(initialCapacity = 1024) {
82
+ this.parts = [];
83
+ this.length = 0;
84
+ this.initialCapacity = initialCapacity;
85
+
86
+ // Node 20+ uses more aggressive chunking
87
+ this.chunkSize = features.isNode20Plus ? 65536 : 16384;
88
+ }
89
+
90
+ append(str) {
91
+ if (str) {
92
+ this.parts.push(str);
93
+ this.length += str.length;
94
+ }
95
+ return this;
96
+ }
97
+
98
+ toString() {
99
+ return this.parts.join('');
100
+ }
101
+
102
+ clear() {
103
+ this.parts = [];
104
+ this.length = 0;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Optimized row buffer for streaming CSV parsing
110
+ * Minimizes allocations on modern Node versions
111
+ */
112
+ class RowBuffer {
113
+ constructor(initialSize = 100) {
114
+ this.rows = [];
115
+ this.currentRow = [];
116
+ this.rowCount = 0;
117
+
118
+ // Pre-allocate on Node 20+
119
+ if (features.isNode20Plus) {
120
+ this.rows = new Array(initialSize);
121
+ this.rows.length = 0;
122
+ }
123
+ }
124
+
125
+ addField(field) {
126
+ this.currentRow.push(field);
127
+ }
128
+
129
+ commitRow() {
130
+ if (this.currentRow.length > 0) {
131
+ this.rows.push(this.currentRow);
132
+ this.rowCount++;
133
+ this.currentRow = [];
134
+ }
135
+ }
136
+
137
+ getRows() {
138
+ return this.rows;
139
+ }
140
+
141
+ clear() {
142
+ this.rows = features.isNode20Plus ? new Array(100) : [];
143
+ this.rows.length = 0;
144
+ this.currentRow = [];
145
+ this.rowCount = 0;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Optimized field parser with char code comparisons
151
+ * Faster than string comparisons on all Node versions
152
+ */
153
+ const CHAR_CODES = {
154
+ QUOTE: 34, // "
155
+ COMMA: 44, // ,
156
+ SEMICOLON: 59, // ;
157
+ TAB: 9, // \t
158
+ PIPE: 124, // |
159
+ NEWLINE: 10, // \n
160
+ CARRIAGE: 13, // \r
161
+ SPACE: 32, // space
162
+ EQUALS: 61, // =
163
+ PLUS: 43, // +
164
+ MINUS: 45, // -
165
+ AT: 64, // @
166
+ BACKSLASH: 92, // \
167
+ APOSTROPHE: 39 // '
168
+ };
169
+
170
+ /**
171
+ * Fast delimiter detection using char codes
172
+ */
173
+ function fastDetectDelimiter(sample, candidates = [';', ',', '\t', '|']) {
174
+ const firstLineEnd = sample.indexOf('\n');
175
+ const firstLine = firstLineEnd > -1 ? sample.slice(0, firstLineEnd) : sample;
176
+
177
+ const candidateCodes = candidates.map(c => c.charCodeAt(0));
178
+ const counts = new Array(candidateCodes.length).fill(0);
179
+
180
+ // Use fast char code iteration on Node 20+
181
+ const len = Math.min(firstLine.length, 10000);
182
+
183
+ for (let i = 0; i < len; i++) {
184
+ const code = firstLine.charCodeAt(i);
185
+ for (let j = 0; j < candidateCodes.length; j++) {
186
+ if (code === candidateCodes[j]) {
187
+ counts[j]++;
188
+ }
189
+ }
190
+ }
191
+
192
+ let maxCount = 0;
193
+ let maxIndex = 0;
194
+
195
+ for (let i = 0; i < counts.length; i++) {
196
+ if (counts[i] > maxCount) {
197
+ maxCount = counts[i];
198
+ maxIndex = i;
199
+ }
200
+ }
201
+
202
+ return candidates[maxIndex];
203
+ }
204
+
205
+ /**
206
+ * Optimized batch processor for large datasets
207
+ * Uses different chunk sizes based on Node version
208
+ */
209
+ function createBatchProcessor(processor, options = {}) {
210
+ const batchSize = options.batchSize || (features.isNode20Plus ? 10000 : 5000);
211
+ const parallelism = options.parallelism || (features.isNode22Plus ? 4 : 2);
212
+
213
+ return async function* processBatches(items) {
214
+ const batches = [];
215
+
216
+ for (let i = 0; i < items.length; i += batchSize) {
217
+ batches.push(items.slice(i, i + batchSize));
218
+ }
219
+
220
+ // Process batches with limited parallelism
221
+ for (let i = 0; i < batches.length; i += parallelism) {
222
+ const chunk = batches.slice(i, i + parallelism);
223
+ const results = await Promise.all(chunk.map(batch => processor(batch)));
224
+
225
+ for (const result of results) {
226
+ yield* result;
227
+ }
228
+ }
229
+ };
230
+ }
231
+
232
+ /**
233
+ * Memory-efficient object pool for row objects
234
+ * Reduces GC pressure on large CSV files
235
+ */
236
+ class ObjectPool {
237
+ constructor(factory, initialSize = 100) {
238
+ this.factory = factory;
239
+ this.pool = [];
240
+ this.inUse = 0;
241
+
242
+ // Pre-warm pool on Node 20+
243
+ if (features.isNode20Plus) {
244
+ for (let i = 0; i < initialSize; i++) {
245
+ this.pool.push(factory());
246
+ }
247
+ }
248
+ }
249
+
250
+ acquire() {
251
+ this.inUse++;
252
+ if (this.pool.length > 0) {
253
+ return this.pool.pop();
254
+ }
255
+ return this.factory();
256
+ }
257
+
258
+ release(obj) {
259
+ this.inUse--;
260
+ // Clear object properties before returning to pool
261
+ for (const key in obj) {
262
+ if (hasOwn(obj, key)) {
263
+ delete obj[key];
264
+ }
265
+ }
266
+ this.pool.push(obj);
267
+ }
268
+
269
+ getStats() {
270
+ return {
271
+ poolSize: this.pool.length,
272
+ inUse: this.inUse
273
+ };
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Fast string escape for CSV values
279
+ * Uses pre-computed regex on all versions
280
+ */
281
+ const QUOTE_REGEX = /"/g;
282
+
283
+ function fastEscapeValue(value, delimiterCode) {
284
+ if (value === null || value === undefined || value === '') {
285
+ return '';
286
+ }
287
+
288
+ const str = typeof value === 'string' ? value : String(value);
289
+ const len = str.length;
290
+
291
+ // Quick scan for special characters using char codes
292
+ let needsQuoting = false;
293
+ let hasQuote = false;
294
+
295
+ for (let i = 0; i < len; i++) {
296
+ const code = str.charCodeAt(i);
297
+ if (code === CHAR_CODES.QUOTE) {
298
+ hasQuote = true;
299
+ needsQuoting = true;
300
+ } else if (code === delimiterCode || code === CHAR_CODES.NEWLINE || code === CHAR_CODES.CARRIAGE) {
301
+ needsQuoting = true;
302
+ }
303
+ }
304
+
305
+ if (!needsQuoting) {
306
+ return str;
307
+ }
308
+
309
+ const escaped = hasQuote ? str.replace(QUOTE_REGEX, '""') : str;
310
+ return `"${escaped}"`;
311
+ }
312
+
313
+ /**
314
+ * Async iterator utilities for streaming
315
+ */
316
+ const asyncIterUtils = {
317
+ /**
318
+ * Map over async iterator with concurrency control (Node 20+)
319
+ */
320
+ async *mapConcurrent(iterator, mapper, concurrency = 4) {
321
+ const pending = [];
322
+
323
+ for await (const item of iterator) {
324
+ pending.push(mapper(item));
325
+
326
+ if (pending.length >= concurrency) {
327
+ const results = await Promise.all(pending.splice(0, concurrency));
328
+ for (const result of results) {
329
+ yield result;
330
+ }
331
+ }
332
+ }
333
+
334
+ if (pending.length > 0) {
335
+ const results = await Promise.all(pending);
336
+ for (const result of results) {
337
+ yield result;
338
+ }
339
+ }
340
+ },
341
+
342
+ /**
343
+ * Batch items from async iterator
344
+ */
345
+ async *batch(iterator, size = 1000) {
346
+ let batch = [];
347
+
348
+ for await (const item of iterator) {
349
+ batch.push(item);
350
+
351
+ if (batch.length >= size) {
352
+ yield batch;
353
+ batch = [];
354
+ }
355
+ }
356
+
357
+ if (batch.length > 0) {
358
+ yield batch;
359
+ }
360
+ }
361
+ };
362
+
363
+ /**
364
+ * Get runtime optimization hints
365
+ */
366
+ function getOptimizationHints() {
367
+ return {
368
+ nodeVersion: `${major}.${minor}`,
369
+ features,
370
+ recommendations: {
371
+ useWebStreams: features.hasWebStreams,
372
+ useStructuredClone: features.hasStructuredClone,
373
+ useLargerBatches: features.isNode20Plus,
374
+ useHigherParallelism: features.isNode22Plus,
375
+ preferredChunkSize: features.isNode24Plus ? 131072 : (features.isNode20Plus ? 65536 : 16384)
376
+ }
377
+ };
378
+ }
379
+
380
+ module.exports = {
381
+ // Feature detection
382
+ features,
383
+ nodeVersion: { major, minor },
384
+
385
+ // Polyfills and optimized functions
386
+ hasOwn,
387
+ deepClone,
388
+ arrayAt,
389
+
390
+ // Classes
391
+ StringBuilderOptimized,
392
+ RowBuffer,
393
+ ObjectPool,
394
+
395
+ // Constants
396
+ CHAR_CODES,
397
+
398
+ // Functions
399
+ fastDetectDelimiter,
400
+ fastEscapeValue,
401
+ createBatchProcessor,
402
+
403
+ // Async utilities
404
+ asyncIterUtils,
405
+
406
+ // Diagnostics
407
+ getOptimizationHints
408
+ };
@@ -13,7 +13,7 @@ function createTextDecoder() {
13
13
  try {
14
14
  const { TextDecoder: UtilTextDecoder } = require('util');
15
15
  return new UtilTextDecoder('utf-8');
16
- } catch (error) {
16
+ } catch (_error) {
17
17
  return null;
18
18
  }
19
19
  }
@@ -24,7 +24,7 @@ function getTransformStream() {
24
24
  }
25
25
  try {
26
26
  return require('stream/web').TransformStream;
27
- } catch (error) {
27
+ } catch (_error) {
28
28
  return null;
29
29
  }
30
30
  }
@@ -411,10 +411,9 @@ class NdjsonParser {
411
411
  let buffer = '';
412
412
 
413
413
  try {
414
- // eslint-disable-next-line no-constant-condition
415
414
  while (true) {
416
415
  const { done, value } = await reader.read();
417
-
416
+
418
417
  if (done) {
419
418
  // Обрабатываем оставшийся буфер
420
419
  /* istanbul ignore next */
@@ -423,7 +422,7 @@ class NdjsonParser {
423
422
  try {
424
423
  JSON.parse(buffer.trim());
425
424
  stats.validLines++;
426
- } catch (error) {
425
+ } catch (_error) {
427
426
  stats.errorLines++;
428
427
  }
429
428
  }
@@ -465,4 +464,4 @@ class NdjsonParser {
465
464
  }
466
465
  }
467
466
 
468
- module.exports = NdjsonParser;
467
+ module.exports = NdjsonParser;