jtcsv 2.2.7 → 3.0.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 (140) hide show
  1. package/README.md +31 -1
  2. package/bin/jtcsv.js +891 -821
  3. package/bin/jtcsv.ts +2534 -0
  4. package/csv-to-json.js +168 -145
  5. package/dist/jtcsv-core.cjs.js +1407 -0
  6. package/dist/jtcsv-core.cjs.js.map +1 -0
  7. package/dist/jtcsv-core.esm.js +1379 -0
  8. package/dist/jtcsv-core.esm.js.map +1 -0
  9. package/dist/jtcsv-core.umd.js +1413 -0
  10. package/dist/jtcsv-core.umd.js.map +1 -0
  11. package/dist/jtcsv-full.cjs.js +1912 -0
  12. package/dist/jtcsv-full.cjs.js.map +1 -0
  13. package/dist/jtcsv-full.esm.js +1880 -0
  14. package/dist/jtcsv-full.esm.js.map +1 -0
  15. package/dist/jtcsv-full.umd.js +1918 -0
  16. package/dist/jtcsv-full.umd.js.map +1 -0
  17. package/dist/jtcsv-workers.esm.js +759 -0
  18. package/dist/jtcsv-workers.esm.js.map +1 -0
  19. package/dist/jtcsv-workers.umd.js +773 -0
  20. package/dist/jtcsv-workers.umd.js.map +1 -0
  21. package/dist/jtcsv.cjs.js +61 -19
  22. package/dist/jtcsv.cjs.js.map +1 -1
  23. package/dist/jtcsv.esm.js +61 -19
  24. package/dist/jtcsv.esm.js.map +1 -1
  25. package/dist/jtcsv.umd.js +61 -19
  26. package/dist/jtcsv.umd.js.map +1 -1
  27. package/errors.js +188 -2
  28. package/examples/advanced/conditional-transformations.js +446 -0
  29. package/examples/advanced/conditional-transformations.ts +446 -0
  30. package/examples/advanced/csv-parser.worker.js +89 -0
  31. package/examples/advanced/csv-parser.worker.ts +89 -0
  32. package/examples/advanced/nested-objects-example.js +306 -0
  33. package/examples/advanced/nested-objects-example.ts +306 -0
  34. package/examples/advanced/performance-optimization.js +504 -0
  35. package/examples/advanced/performance-optimization.ts +504 -0
  36. package/examples/advanced/run-demo-server.js +116 -0
  37. package/examples/advanced/run-demo-server.ts +116 -0
  38. package/examples/advanced/web-worker-usage.html +874 -0
  39. package/examples/async-multithreaded-example.ts +335 -0
  40. package/examples/cli-advanced-usage.md +288 -0
  41. package/examples/cli-batch-processing.ts +38 -0
  42. package/examples/cli-tool.js +0 -3
  43. package/examples/cli-tool.ts +183 -0
  44. package/examples/error-handling.js +21 -7
  45. package/examples/error-handling.ts +356 -0
  46. package/examples/express-api.js +0 -3
  47. package/examples/express-api.ts +164 -0
  48. package/examples/large-dataset-example.js +0 -3
  49. package/examples/large-dataset-example.ts +204 -0
  50. package/examples/ndjson-processing.js +1 -1
  51. package/examples/ndjson-processing.ts +456 -0
  52. package/examples/plugin-excel-exporter.js +3 -4
  53. package/examples/plugin-excel-exporter.ts +406 -0
  54. package/examples/react-integration.tsx +637 -0
  55. package/examples/schema-validation.ts +640 -0
  56. package/examples/simple-usage.js +254 -254
  57. package/examples/simple-usage.ts +194 -0
  58. package/examples/streaming-example.js +4 -5
  59. package/examples/streaming-example.ts +419 -0
  60. package/examples/web-workers-advanced.ts +28 -0
  61. package/index.d.ts +1 -3
  62. package/index.js +15 -1
  63. package/json-save.js +9 -3
  64. package/json-to-csv.js +168 -21
  65. package/package.json +69 -10
  66. package/plugins/express-middleware/README.md +21 -2
  67. package/plugins/express-middleware/example.js +3 -4
  68. package/plugins/express-middleware/example.ts +135 -0
  69. package/plugins/express-middleware/index.d.ts +1 -1
  70. package/plugins/express-middleware/index.js +270 -118
  71. package/plugins/express-middleware/index.ts +557 -0
  72. package/plugins/fastify-plugin/index.js +2 -4
  73. package/plugins/fastify-plugin/index.ts +443 -0
  74. package/plugins/hono/index.ts +226 -0
  75. package/plugins/nestjs/index.ts +201 -0
  76. package/plugins/nextjs-api/examples/ConverterComponent.tsx +386 -0
  77. package/plugins/nextjs-api/examples/api-convert.js +0 -2
  78. package/plugins/nextjs-api/examples/api-convert.ts +67 -0
  79. package/plugins/nextjs-api/index.tsx +339 -0
  80. package/plugins/nextjs-api/route.js +2 -3
  81. package/plugins/nextjs-api/route.ts +370 -0
  82. package/plugins/nuxt/index.ts +94 -0
  83. package/plugins/nuxt/runtime/composables/useJtcsv.ts +100 -0
  84. package/plugins/nuxt/runtime/plugin.ts +71 -0
  85. package/plugins/remix/index.js +1 -1
  86. package/plugins/remix/index.ts +260 -0
  87. package/plugins/sveltekit/index.js +1 -1
  88. package/plugins/sveltekit/index.ts +301 -0
  89. package/plugins/trpc/index.ts +267 -0
  90. package/src/browser/browser-functions.ts +402 -0
  91. package/src/browser/core.js +92 -0
  92. package/src/browser/core.ts +152 -0
  93. package/src/browser/csv-to-json-browser.d.ts +3 -0
  94. package/src/browser/csv-to-json-browser.js +36 -14
  95. package/src/browser/csv-to-json-browser.ts +264 -0
  96. package/src/browser/errors-browser.ts +303 -0
  97. package/src/browser/extensions/plugins.js +92 -0
  98. package/src/browser/extensions/plugins.ts +93 -0
  99. package/src/browser/extensions/workers.js +39 -0
  100. package/src/browser/extensions/workers.ts +39 -0
  101. package/src/browser/globals.d.ts +5 -0
  102. package/src/browser/index.ts +192 -0
  103. package/src/browser/json-to-csv-browser.d.ts +3 -0
  104. package/src/browser/json-to-csv-browser.js +13 -3
  105. package/src/browser/json-to-csv-browser.ts +262 -0
  106. package/src/browser/streams.js +12 -2
  107. package/src/browser/streams.ts +336 -0
  108. package/src/browser/workers/csv-parser.worker.ts +377 -0
  109. package/src/browser/workers/worker-pool.ts +548 -0
  110. package/src/core/delimiter-cache.js +22 -8
  111. package/src/core/delimiter-cache.ts +310 -0
  112. package/src/core/node-optimizations.ts +449 -0
  113. package/src/core/plugin-system.js +29 -11
  114. package/src/core/plugin-system.ts +400 -0
  115. package/src/core/transform-hooks.ts +558 -0
  116. package/src/engines/fast-path-engine-new.ts +347 -0
  117. package/src/engines/fast-path-engine.ts +854 -0
  118. package/src/errors.ts +72 -0
  119. package/src/formats/ndjson-parser.ts +469 -0
  120. package/src/formats/tsv-parser.ts +334 -0
  121. package/src/index-with-plugins.js +16 -9
  122. package/src/index-with-plugins.ts +395 -0
  123. package/src/types/index.ts +255 -0
  124. package/src/utils/bom-utils.js +259 -0
  125. package/src/utils/bom-utils.ts +373 -0
  126. package/src/utils/encoding-support.js +124 -0
  127. package/src/utils/encoding-support.ts +155 -0
  128. package/src/utils/schema-validator.js +19 -19
  129. package/src/utils/schema-validator.ts +819 -0
  130. package/src/utils/transform-loader.js +1 -1
  131. package/src/utils/transform-loader.ts +389 -0
  132. package/src/utils/zod-adapter.js +170 -0
  133. package/src/utils/zod-adapter.ts +280 -0
  134. package/src/web-server/index.js +10 -10
  135. package/src/web-server/index.ts +683 -0
  136. package/src/workers/csv-multithreaded.ts +310 -0
  137. package/src/workers/csv-parser.worker.ts +227 -0
  138. package/src/workers/worker-pool.ts +409 -0
  139. package/stream-csv-to-json.js +26 -8
  140. package/stream-json-to-csv.js +1 -0
@@ -0,0 +1,334 @@
1
+ /**
2
+ * TSV (Tab-Separated Values) парсер
3
+ * Специализированная поддержка TSV формата
4
+ *
5
+ * @version 1.0.0
6
+ * @date 2026-01-23
7
+ */
8
+
9
+ import fs from "fs";
10
+ import path from "path";
11
+ import { csvToJson } from "../../csv-to-json";
12
+ import { jsonToCsv } from "../../json-to-csv";
13
+ import { ValidationError, SecurityError, FileSystemError } from "../../errors";
14
+ import { createJsonToCsvStream } from "../../stream-json-to-csv";
15
+ import { createCsvToJsonStream } from "../../stream-csv-to-json";
16
+
17
+ function validateTsvFilePath(filePath) {
18
+ if (typeof filePath !== 'string' || filePath.trim() === '') {
19
+ throw new ValidationError('File path must be a non-empty string');
20
+ }
21
+
22
+ if (!filePath.toLowerCase().endsWith('.tsv')) {
23
+ throw new ValidationError('File must have .tsv extension');
24
+ }
25
+
26
+ const normalizedPath = path.normalize(filePath);
27
+ if (normalizedPath.includes('..') ||
28
+ /\\\.\.\\|\/\.\.\//.test(filePath) ||
29
+ filePath.startsWith('..') ||
30
+ filePath.includes('/..')) {
31
+ throw new SecurityError('Directory traversal detected in file path');
32
+ }
33
+
34
+ return path.resolve(filePath);
35
+ }
36
+
37
+ class TsvParser {
38
+ /**
39
+ * Конвертирует массив объектов в TSV строку
40
+ * @param {Array} data - Массив объектов
41
+ * @param {Object} options - Опции форматирования
42
+ * @returns {string} TSV строка
43
+ *
44
+ * @example
45
+ * const data = [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }];
46
+ * const tsv = TsvParser.jsonToTsv(data);
47
+ * // Результат: "id\tname\n1\tJohn\n2\tJane"
48
+ */
49
+ static jsonToTsv(data, options = {}) {
50
+ const defaultOptions = {
51
+ delimiter: '\t',
52
+ includeHeaders: true,
53
+ ...options
54
+ };
55
+
56
+ return jsonToCsv(data, defaultOptions);
57
+ }
58
+
59
+ /**
60
+ * Конвертирует TSV строку в массив объектов
61
+ * @param {string} tsvString - TSV строка
62
+ * @param {Object} options - Опции парсинга
63
+ * @returns {Array} Массив объектов
64
+ *
65
+ * @example
66
+ * const tsv = "id\tname\n1\tJohn\n2\tJane";
67
+ * const data = TsvParser.tsvToJson(tsv);
68
+ * // Результат: [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]
69
+ */
70
+ static tsvToJson(tsvString, options = {}) {
71
+ const defaultOptions = {
72
+ delimiter: '\t',
73
+ autoDetect: false,
74
+ hasHeaders: true,
75
+ ...options
76
+ };
77
+
78
+ return csvToJson(tsvString, defaultOptions);
79
+ }
80
+
81
+ /**
82
+ * Автоматически определяет является ли строка TSV
83
+ * @param {string} sample - Образец данных
84
+ * @returns {boolean} True если это TSV
85
+ */
86
+ static isTsv(sample) {
87
+ if (!sample || typeof sample !== 'string') {
88
+ return false;
89
+ }
90
+
91
+ const lines = sample.split('\n').slice(0, 10);
92
+ let tabCount = 0;
93
+ let commaCount = 0;
94
+ let semicolonCount = 0;
95
+
96
+ for (const line of lines) {
97
+ if (line.trim() === '') {
98
+ continue;
99
+ }
100
+
101
+ // Считаем разделители
102
+ tabCount += (line.match(/\t/g) || []).length;
103
+ commaCount += (line.match(/,/g) || []).length;
104
+ semicolonCount += (line.match(/;/g) || []).length;
105
+ }
106
+
107
+ // Если табуляций больше чем других разделителей, считаем это TSV
108
+ return tabCount > commaCount && tabCount > semicolonCount;
109
+ }
110
+
111
+ /**
112
+ * Создает TransformStream для конвертации JSON в TSV
113
+ * @param {Object} options - Опции конвертации
114
+ * @returns {TransformStream} Transform stream
115
+ */
116
+ static createJsonToTsvStream(options = {}) {
117
+ return createJsonToCsvStream({
118
+ delimiter: '\t',
119
+ ...options
120
+ });
121
+ }
122
+
123
+ /**
124
+ * Создает TransformStream для конвертации TSV в JSON
125
+ * @param {Object} options - Опции конвертации
126
+ * @returns {TransformStream} Transform stream
127
+ */
128
+ static createTsvToJsonStream(options = {}) {
129
+ return createCsvToJsonStream({
130
+ delimiter: '\t',
131
+ autoDetect: false,
132
+ ...options
133
+ });
134
+ }
135
+
136
+ /**
137
+ * Читает TSV файл и конвертирует в JSON
138
+ * @param {string} filePath - Путь к TSV файлу
139
+ * @param {Object} options - Опции парсинга
140
+ * @returns {Promise<Array>} Promise с массивом объектов
141
+ */
142
+ static async readTsvAsJson(filePath, options = {}) {
143
+ const safePath = validateTsvFilePath(filePath);
144
+
145
+ try {
146
+ const tsvContent = await fs.promises.readFile(safePath, 'utf8');
147
+ return csvToJson(tsvContent, {
148
+ delimiter: '\t',
149
+ autoDetect: false,
150
+ ...options
151
+ });
152
+ } catch (error) {
153
+ if (error instanceof ValidationError || error instanceof SecurityError) {
154
+ throw error;
155
+ }
156
+ if (error.code === 'ENOENT') {
157
+ throw new FileSystemError(`File not found: ${safePath}`, error);
158
+ }
159
+ if (error.code === 'EACCES') {
160
+ throw new FileSystemError(`Permission denied: ${safePath}`, error);
161
+ }
162
+ if (error.code === 'EISDIR') {
163
+ throw new FileSystemError(`Path is a directory: ${safePath}`, error);
164
+ }
165
+ throw new FileSystemError(`Failed to read TSV file: ${error.message}`, error);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Синхронно читает TSV файл и конвертирует в JSON
171
+ * @param {string} filePath - Путь к TSV файлу
172
+ * @param {Object} options - Опции парсинга
173
+ * @returns {Array} Массив объектов
174
+ */
175
+ static readTsvAsJsonSync(filePath, options = {}) {
176
+ const safePath = validateTsvFilePath(filePath);
177
+
178
+ try {
179
+ const tsvContent = fs.readFileSync(safePath, 'utf8');
180
+ return csvToJson(tsvContent, {
181
+ delimiter: '\t',
182
+ autoDetect: false,
183
+ ...options
184
+ });
185
+ } catch (error) {
186
+ if (error instanceof ValidationError || error instanceof SecurityError) {
187
+ throw error;
188
+ }
189
+ if (error.code === 'ENOENT') {
190
+ throw new FileSystemError(`File not found: ${safePath}`, error);
191
+ }
192
+ if (error.code === 'EACCES') {
193
+ throw new FileSystemError(`Permission denied: ${safePath}`, error);
194
+ }
195
+ if (error.code === 'EISDIR') {
196
+ throw new FileSystemError(`Path is a directory: ${safePath}`, error);
197
+ }
198
+ throw new FileSystemError(`Failed to read TSV file: ${error.message}`, error);
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Сохраняет массив объектов как TSV файл
204
+ * @param {Array} data - Массив объектов
205
+ * @param {string} filePath - Путь для сохранения
206
+ * @param {Object} options - Опции сохранения
207
+ * @returns {Promise<void>}
208
+ */
209
+ static async saveAsTsv(data, filePath, options = {}) {
210
+ const safePath = validateTsvFilePath(filePath);
211
+ const tsvContent = this.jsonToTsv(data, options);
212
+ const dir = path.dirname(safePath);
213
+
214
+ try {
215
+ await fs.promises.mkdir(dir, { recursive: true });
216
+ await fs.promises.writeFile(safePath, tsvContent, 'utf8');
217
+ return safePath;
218
+ } catch (error) {
219
+ if (error.code === 'ENOENT') {
220
+ throw new FileSystemError(`Directory does not exist: ${dir}`, error);
221
+ }
222
+ if (error.code === 'EACCES') {
223
+ throw new FileSystemError(`Permission denied: ${safePath}`, error);
224
+ }
225
+ if (error.code === 'ENOSPC') {
226
+ throw new FileSystemError(`No space left on device: ${safePath}`, error);
227
+ }
228
+ throw new FileSystemError(`Failed to save TSV file: ${error.message}`, error);
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Синхронно сохраняет массив объектов как TSV файл
234
+ * @param {Array} data - Массив объектов
235
+ * @param {string} filePath - Путь для сохранения
236
+ * @param {Object} options - Опции сохранения
237
+ */
238
+ static saveAsTsvSync(data, filePath, options = {}) {
239
+ const safePath = validateTsvFilePath(filePath);
240
+ const tsvContent = this.jsonToTsv(data, options);
241
+
242
+ fs.mkdirSync(path.dirname(safePath), { recursive: true });
243
+ fs.writeFileSync(safePath, tsvContent, 'utf8');
244
+ }
245
+
246
+ /**
247
+ * Валидирует TSV строку
248
+ * @param {string} tsvString - TSV строка для валидации
249
+ * @param {Object} options - Опции валидации
250
+ * @returns {Object} Результат валидации
251
+ */
252
+ static validateTsv(tsvString, options: any = {}) {
253
+ const { requireConsistentColumns = true } = options;
254
+
255
+ if (!tsvString || typeof tsvString !== 'string') {
256
+ return {
257
+ valid: false,
258
+ error: 'Input must be a non-empty string',
259
+ details: { inputType: typeof tsvString }
260
+ };
261
+ }
262
+
263
+ const lines = tsvString.split('\n').filter(line => line.trim() !== '');
264
+
265
+ if (lines.length === 0) {
266
+ return {
267
+ valid: false,
268
+ error: 'No data found in TSV',
269
+ details: { lineCount: 0 }
270
+ };
271
+ }
272
+
273
+ const columnCounts = [];
274
+ const errors = [];
275
+
276
+ for (let i = 0; i < lines.length; i++) {
277
+ const line = lines[i];
278
+ const columns = line.split('\t');
279
+ columnCounts.push(columns.length);
280
+
281
+ // Проверяем наличие пустых полей (если требуется)
282
+ if (options.disallowEmptyFields) {
283
+ const emptyFields = columns.filter(field => field.trim() === '');
284
+ if (emptyFields.length > 0) {
285
+ errors.push({
286
+ line: i + 1,
287
+ error: `Found ${emptyFields.length} empty field(s)`,
288
+ fields: emptyFields.map((_, idx) => idx + 1)
289
+ });
290
+ }
291
+ }
292
+ }
293
+
294
+ // Проверяем консистентность колонки
295
+ if (requireConsistentColumns && columnCounts.length > 1) {
296
+ const firstCount = columnCounts[0];
297
+ const inconsistentLines = [];
298
+
299
+ for (let i = 1; i < columnCounts.length; i++) {
300
+ if (columnCounts[i] !== firstCount) {
301
+ inconsistentLines.push({
302
+ line: i + 1,
303
+ expected: firstCount,
304
+ actual: columnCounts[i]
305
+ });
306
+ }
307
+ }
308
+
309
+ if (inconsistentLines.length > 0) {
310
+ errors.push({
311
+ error: 'Inconsistent column count',
312
+ details: inconsistentLines
313
+ });
314
+ }
315
+ }
316
+
317
+ /* istanbul ignore next */
318
+ const totalColumns = columnCounts[0] || 0;
319
+
320
+ return {
321
+ valid: errors.length === 0,
322
+ stats: {
323
+ totalLines: lines.length,
324
+ totalColumns,
325
+ minColumns: Math.min(...columnCounts),
326
+ maxColumns: Math.max(...columnCounts),
327
+ consistentColumns: new Set(columnCounts).size === 1
328
+ },
329
+ errors: errors.length > 0 ? errors : undefined
330
+ };
331
+ }
332
+ }
333
+
334
+ export default TsvParser;
@@ -48,7 +48,9 @@ class JtcsvWithPlugins {
48
48
  const structure = this.fastPathEngine.analyzeStructure(sample, context.options);
49
49
 
50
50
  context.metadata.fastPathStructure = structure;
51
- console.log(`🚀 Используется ${structure.recommendedEngine} парсер`);
51
+ if (process.env.NODE_ENV === 'development') {
52
+ console.log(`🚀 Используется ${structure.recommendedEngine} парсер`);
53
+ }
52
54
  }
53
55
  return csv;
54
56
  },
@@ -120,19 +122,27 @@ class JtcsvWithPlugins {
120
122
  description: 'Логирование операций',
121
123
  hooks: {
122
124
  'before:csvToJson': (csv, context) => {
123
- console.log(`📥 Начало csvToJson, размер: ${csv.length} байт`);
125
+ if (process.env.NODE_ENV === 'development') {
126
+ console.log(`📥 Начало csvToJson, размер: ${csv.length} байт`);
127
+ }
124
128
  return csv;
125
129
  },
126
130
  'after:csvToJson': (result, context) => {
127
- console.log(`📤 Завершение csvToJson, результат: ${result.length} записей`);
131
+ if (process.env.NODE_ENV === 'development') {
132
+ console.log(`📤 Завершение csvToJson, результат: ${result.length} записей`);
133
+ }
128
134
  return result;
129
135
  },
130
136
  'before:jsonToCsv': (json, context) => {
131
- console.log(`📥 Начало jsonToCsv, записей: ${json.length}`);
137
+ if (process.env.NODE_ENV === 'development') {
138
+ console.log(`📥 Начало jsonToCsv, записей: ${json.length}`);
139
+ }
132
140
  return json;
133
141
  },
134
142
  'after:jsonToCsv': (csv, context) => {
135
- console.log(`📤 Завершение jsonToCsv, размер: ${csv.length} байт`);
143
+ if (process.env.NODE_ENV === 'development') {
144
+ console.log(`📤 Завершение jsonToCsv, размер: ${csv.length} байт`);
145
+ }
136
146
  return csv;
137
147
  }
138
148
  }
@@ -365,7 +375,4 @@ module.exports.FastPathEngine = FastPathEngine;
365
375
  module.exports.NdjsonParser = NdjsonParser;
366
376
 
367
377
  // Экспортируем фабричный метод
368
- module.exports.create = JtcsvWithPlugins.create;
369
-
370
-
371
-
378
+ module.exports.create = JtcsvWithPlugins.create;