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.
- package/README.md +31 -1
- package/bin/jtcsv.js +891 -821
- package/bin/jtcsv.ts +2534 -0
- package/csv-to-json.js +168 -145
- package/dist/jtcsv-core.cjs.js +1407 -0
- package/dist/jtcsv-core.cjs.js.map +1 -0
- package/dist/jtcsv-core.esm.js +1379 -0
- package/dist/jtcsv-core.esm.js.map +1 -0
- package/dist/jtcsv-core.umd.js +1413 -0
- package/dist/jtcsv-core.umd.js.map +1 -0
- package/dist/jtcsv-full.cjs.js +1912 -0
- package/dist/jtcsv-full.cjs.js.map +1 -0
- package/dist/jtcsv-full.esm.js +1880 -0
- package/dist/jtcsv-full.esm.js.map +1 -0
- package/dist/jtcsv-full.umd.js +1918 -0
- package/dist/jtcsv-full.umd.js.map +1 -0
- package/dist/jtcsv-workers.esm.js +759 -0
- package/dist/jtcsv-workers.esm.js.map +1 -0
- package/dist/jtcsv-workers.umd.js +773 -0
- package/dist/jtcsv-workers.umd.js.map +1 -0
- package/dist/jtcsv.cjs.js +61 -19
- package/dist/jtcsv.cjs.js.map +1 -1
- package/dist/jtcsv.esm.js +61 -19
- package/dist/jtcsv.esm.js.map +1 -1
- package/dist/jtcsv.umd.js +61 -19
- package/dist/jtcsv.umd.js.map +1 -1
- package/errors.js +188 -2
- package/examples/advanced/conditional-transformations.js +446 -0
- package/examples/advanced/conditional-transformations.ts +446 -0
- package/examples/advanced/csv-parser.worker.js +89 -0
- package/examples/advanced/csv-parser.worker.ts +89 -0
- package/examples/advanced/nested-objects-example.js +306 -0
- package/examples/advanced/nested-objects-example.ts +306 -0
- package/examples/advanced/performance-optimization.js +504 -0
- package/examples/advanced/performance-optimization.ts +504 -0
- package/examples/advanced/run-demo-server.js +116 -0
- package/examples/advanced/run-demo-server.ts +116 -0
- package/examples/advanced/web-worker-usage.html +874 -0
- package/examples/async-multithreaded-example.ts +335 -0
- package/examples/cli-advanced-usage.md +288 -0
- package/examples/cli-batch-processing.ts +38 -0
- package/examples/cli-tool.js +0 -3
- package/examples/cli-tool.ts +183 -0
- package/examples/error-handling.js +21 -7
- package/examples/error-handling.ts +356 -0
- package/examples/express-api.js +0 -3
- package/examples/express-api.ts +164 -0
- package/examples/large-dataset-example.js +0 -3
- package/examples/large-dataset-example.ts +204 -0
- package/examples/ndjson-processing.js +1 -1
- package/examples/ndjson-processing.ts +456 -0
- package/examples/plugin-excel-exporter.js +3 -4
- package/examples/plugin-excel-exporter.ts +406 -0
- package/examples/react-integration.tsx +637 -0
- package/examples/schema-validation.ts +640 -0
- package/examples/simple-usage.js +254 -254
- package/examples/simple-usage.ts +194 -0
- package/examples/streaming-example.js +4 -5
- package/examples/streaming-example.ts +419 -0
- package/examples/web-workers-advanced.ts +28 -0
- package/index.d.ts +1 -3
- package/index.js +15 -1
- package/json-save.js +9 -3
- package/json-to-csv.js +168 -21
- package/package.json +69 -10
- package/plugins/express-middleware/README.md +21 -2
- package/plugins/express-middleware/example.js +3 -4
- package/plugins/express-middleware/example.ts +135 -0
- package/plugins/express-middleware/index.d.ts +1 -1
- package/plugins/express-middleware/index.js +270 -118
- package/plugins/express-middleware/index.ts +557 -0
- package/plugins/fastify-plugin/index.js +2 -4
- package/plugins/fastify-plugin/index.ts +443 -0
- package/plugins/hono/index.ts +226 -0
- package/plugins/nestjs/index.ts +201 -0
- package/plugins/nextjs-api/examples/ConverterComponent.tsx +386 -0
- package/plugins/nextjs-api/examples/api-convert.js +0 -2
- package/plugins/nextjs-api/examples/api-convert.ts +67 -0
- package/plugins/nextjs-api/index.tsx +339 -0
- package/plugins/nextjs-api/route.js +2 -3
- package/plugins/nextjs-api/route.ts +370 -0
- package/plugins/nuxt/index.ts +94 -0
- package/plugins/nuxt/runtime/composables/useJtcsv.ts +100 -0
- package/plugins/nuxt/runtime/plugin.ts +71 -0
- package/plugins/remix/index.js +1 -1
- package/plugins/remix/index.ts +260 -0
- package/plugins/sveltekit/index.js +1 -1
- package/plugins/sveltekit/index.ts +301 -0
- package/plugins/trpc/index.ts +267 -0
- package/src/browser/browser-functions.ts +402 -0
- package/src/browser/core.js +92 -0
- package/src/browser/core.ts +152 -0
- package/src/browser/csv-to-json-browser.d.ts +3 -0
- package/src/browser/csv-to-json-browser.js +36 -14
- package/src/browser/csv-to-json-browser.ts +264 -0
- package/src/browser/errors-browser.ts +303 -0
- package/src/browser/extensions/plugins.js +92 -0
- package/src/browser/extensions/plugins.ts +93 -0
- package/src/browser/extensions/workers.js +39 -0
- package/src/browser/extensions/workers.ts +39 -0
- package/src/browser/globals.d.ts +5 -0
- package/src/browser/index.ts +192 -0
- package/src/browser/json-to-csv-browser.d.ts +3 -0
- package/src/browser/json-to-csv-browser.js +13 -3
- package/src/browser/json-to-csv-browser.ts +262 -0
- package/src/browser/streams.js +12 -2
- package/src/browser/streams.ts +336 -0
- package/src/browser/workers/csv-parser.worker.ts +377 -0
- package/src/browser/workers/worker-pool.ts +548 -0
- package/src/core/delimiter-cache.js +22 -8
- package/src/core/delimiter-cache.ts +310 -0
- package/src/core/node-optimizations.ts +449 -0
- package/src/core/plugin-system.js +29 -11
- package/src/core/plugin-system.ts +400 -0
- package/src/core/transform-hooks.ts +558 -0
- package/src/engines/fast-path-engine-new.ts +347 -0
- package/src/engines/fast-path-engine.ts +854 -0
- package/src/errors.ts +72 -0
- package/src/formats/ndjson-parser.ts +469 -0
- package/src/formats/tsv-parser.ts +334 -0
- package/src/index-with-plugins.js +16 -9
- package/src/index-with-plugins.ts +395 -0
- package/src/types/index.ts +255 -0
- package/src/utils/bom-utils.js +259 -0
- package/src/utils/bom-utils.ts +373 -0
- package/src/utils/encoding-support.js +124 -0
- package/src/utils/encoding-support.ts +155 -0
- package/src/utils/schema-validator.js +19 -19
- package/src/utils/schema-validator.ts +819 -0
- package/src/utils/transform-loader.js +1 -1
- package/src/utils/transform-loader.ts +389 -0
- package/src/utils/zod-adapter.js +170 -0
- package/src/utils/zod-adapter.ts +280 -0
- package/src/web-server/index.js +10 -10
- package/src/web-server/index.ts +683 -0
- package/src/workers/csv-multithreaded.ts +310 -0
- package/src/workers/csv-parser.worker.ts +227 -0
- package/src/workers/worker-pool.ts +409 -0
- package/stream-csv-to-json.js +26 -8
- package/stream-json-to-csv.js +1 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Кэширование результатов авто-детектирования разделителя
|
|
3
|
+
* Использует WeakMap и LRU кэш для оптимизации производительности
|
|
4
|
+
*
|
|
5
|
+
* @version 1.0.0
|
|
6
|
+
* @date 2026-01-23
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
interface CacheStats {
|
|
10
|
+
hits: number;
|
|
11
|
+
misses: number;
|
|
12
|
+
evictions: number;
|
|
13
|
+
size: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface CacheStatsWithRates extends CacheStats {
|
|
17
|
+
hitRate: number;
|
|
18
|
+
totalRequests: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class DelimiterCache {
|
|
22
|
+
private weakMap: WeakMap<object, string>;
|
|
23
|
+
private lruCache: Map<string, string>;
|
|
24
|
+
private maxSize: number;
|
|
25
|
+
private stats: CacheStats;
|
|
26
|
+
|
|
27
|
+
constructor(maxSize: number = 100) {
|
|
28
|
+
this.weakMap = new WeakMap();
|
|
29
|
+
this.lruCache = new Map();
|
|
30
|
+
this.maxSize = maxSize;
|
|
31
|
+
this.stats = {
|
|
32
|
+
hits: 0,
|
|
33
|
+
misses: 0,
|
|
34
|
+
evictions: 0,
|
|
35
|
+
size: 0
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Генерирует ключ кэша на основе строки и кандидатов
|
|
41
|
+
* @private
|
|
42
|
+
*/
|
|
43
|
+
private _generateKey(csv: string, candidates: string[]): string {
|
|
44
|
+
// Используем хэш первых 1000 символов для производительности
|
|
45
|
+
const sample = csv.substring(0, Math.min(1000, csv.length));
|
|
46
|
+
const candidatesKey = candidates.join(',');
|
|
47
|
+
return `${this._hashString(sample)}:${candidatesKey}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Простая хэш-функция для строк
|
|
52
|
+
* @private
|
|
53
|
+
*/
|
|
54
|
+
private _hashString(str: string): string {
|
|
55
|
+
let hash = 0;
|
|
56
|
+
for (let i = 0; i < str.length; i++) {
|
|
57
|
+
const char = str.charCodeAt(i);
|
|
58
|
+
hash = ((hash << 5) - hash) + char;
|
|
59
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
60
|
+
}
|
|
61
|
+
return hash.toString(36);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Получает значение из кэша
|
|
66
|
+
* @param csv - CSV строка
|
|
67
|
+
* @param candidates - Кандидаты разделителей
|
|
68
|
+
* @returns Найденный разделитель или undefined
|
|
69
|
+
*/
|
|
70
|
+
get(csv: string, candidates: string[]): string | null {
|
|
71
|
+
// Сначала проверяем WeakMap (для объектов)
|
|
72
|
+
if (typeof csv === 'object' && csv !== null) {
|
|
73
|
+
const result = this.weakMap.get(csv as any);
|
|
74
|
+
if (result !== undefined) {
|
|
75
|
+
this.stats.hits++;
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Проверяем LRU кэш
|
|
81
|
+
const key = this._generateKey(csv, candidates);
|
|
82
|
+
const result = this.lruCache.get(key);
|
|
83
|
+
|
|
84
|
+
if (result !== undefined) {
|
|
85
|
+
// Обновляем позицию в LRU (перемещаем в конец)
|
|
86
|
+
this.lruCache.delete(key);
|
|
87
|
+
this.lruCache.set(key, result);
|
|
88
|
+
this.stats.hits++;
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.stats.misses++;
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Сохраняет значение в кэш
|
|
98
|
+
* @param csv - CSV строка
|
|
99
|
+
* @param candidates - Кандидаты разделителей
|
|
100
|
+
* @param delimiter - Найденный разделитель
|
|
101
|
+
*/
|
|
102
|
+
set(csv: string, candidates: string[], delimiter: string): void {
|
|
103
|
+
// Сохраняем в WeakMap для объектов
|
|
104
|
+
if (typeof csv === 'object' && csv !== null) {
|
|
105
|
+
this.weakMap.set(csv as any, delimiter);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Сохраняем в LRU кэш
|
|
109
|
+
const key = this._generateKey(csv, candidates);
|
|
110
|
+
|
|
111
|
+
// Проверяем размер кэша
|
|
112
|
+
if (this.lruCache.size >= this.maxSize) {
|
|
113
|
+
// Удаляем самый старый элемент (первый в Map)
|
|
114
|
+
const firstKey = this.lruCache.keys().next().value;
|
|
115
|
+
if (firstKey) {
|
|
116
|
+
this.lruCache.delete(firstKey);
|
|
117
|
+
this.stats.evictions++;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.lruCache.set(key, delimiter);
|
|
122
|
+
this.stats.size = this.lruCache.size;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Очищает кэш
|
|
127
|
+
*/
|
|
128
|
+
clear(): void {
|
|
129
|
+
this.weakMap = new WeakMap();
|
|
130
|
+
this.lruCache.clear();
|
|
131
|
+
this.stats = {
|
|
132
|
+
hits: 0,
|
|
133
|
+
misses: 0,
|
|
134
|
+
evictions: 0,
|
|
135
|
+
size: 0
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Возвращает статистику кэша
|
|
141
|
+
*/
|
|
142
|
+
getStats(): CacheStatsWithRates {
|
|
143
|
+
const totalRequests = this.stats.hits + this.stats.misses;
|
|
144
|
+
return {
|
|
145
|
+
...this.stats,
|
|
146
|
+
totalRequests,
|
|
147
|
+
hitRate: totalRequests > 0 ? (this.stats.hits / totalRequests) * 100 : 0
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Проверяет наличие значения в кэше
|
|
153
|
+
*/
|
|
154
|
+
has(csv: string, candidates: string[]): boolean {
|
|
155
|
+
if (typeof csv === 'object' && csv !== null) {
|
|
156
|
+
return this.weakMap.has(csv as any);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const key = this._generateKey(csv, candidates);
|
|
160
|
+
return this.lruCache.has(key);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Удаляет значение из кэша
|
|
165
|
+
*/
|
|
166
|
+
delete(csv: string, candidates: string[]): boolean {
|
|
167
|
+
let deleted = false;
|
|
168
|
+
|
|
169
|
+
if (typeof csv === 'object' && csv !== null) {
|
|
170
|
+
deleted = this.weakMap.delete(csv as any);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const key = this._generateKey(csv, candidates);
|
|
174
|
+
if (this.lruCache.delete(key)) {
|
|
175
|
+
deleted = true;
|
|
176
|
+
this.stats.size = this.lruCache.size;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return deleted;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Возвращает размер кэша
|
|
184
|
+
*/
|
|
185
|
+
get size(): number {
|
|
186
|
+
return this.lruCache.size;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Асинхронная версия get
|
|
191
|
+
*/
|
|
192
|
+
async getAsync(csv: string, candidates: string[]): Promise<string | null> {
|
|
193
|
+
return this.get(csv, candidates);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Асинхронная версия set
|
|
198
|
+
*/
|
|
199
|
+
async setAsync(csv: string, candidates: string[], delimiter: string): Promise<void> {
|
|
200
|
+
return this.set(csv, candidates, delimiter);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Асинхронная версия clear
|
|
205
|
+
*/
|
|
206
|
+
async clearAsync(): Promise<void> {
|
|
207
|
+
return this.clear();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Оптимизированная версия autoDetectDelimiter с кэшированием
|
|
212
|
+
*/
|
|
213
|
+
static autoDetectDelimiter(
|
|
214
|
+
csv: string,
|
|
215
|
+
candidates: string[] = [';', ',', '\t', '|'],
|
|
216
|
+
cache: DelimiterCache | null = null
|
|
217
|
+
): string {
|
|
218
|
+
if (!csv || typeof csv !== 'string') {
|
|
219
|
+
return ';';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Проверяем кэш если он предоставлен
|
|
223
|
+
if (cache) {
|
|
224
|
+
const cached = cache.get(csv, candidates);
|
|
225
|
+
if (cached !== null) {
|
|
226
|
+
return cached;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const lines = csv.split('\n').filter(line => line.trim().length > 0);
|
|
231
|
+
|
|
232
|
+
if (lines.length === 0) {
|
|
233
|
+
return ';';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Рспользуем первую непустую строку для детектирования
|
|
237
|
+
const firstLine = lines[0];
|
|
238
|
+
|
|
239
|
+
// Быстрый подсчёт вхождений кандидатов за один проход
|
|
240
|
+
const counts: Record<string, number> = {};
|
|
241
|
+
const candidateSet = new Set(candidates);
|
|
242
|
+
for (let i = 0; i < firstLine.length; i++) {
|
|
243
|
+
const char = firstLine[i];
|
|
244
|
+
if (candidateSet.has(char)) {
|
|
245
|
+
counts[char] = (counts[char] || 0) + 1;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
for (const delim of candidates) {
|
|
250
|
+
if (!(delim in counts)) {
|
|
251
|
+
counts[delim] = 0;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let maxCount = -1;
|
|
256
|
+
let detectedDelimiter = ';';
|
|
257
|
+
const maxDelimiters: string[] = [];
|
|
258
|
+
|
|
259
|
+
for (const [delim, count] of Object.entries(counts)) {
|
|
260
|
+
if (count > maxCount) {
|
|
261
|
+
maxCount = count;
|
|
262
|
+
maxDelimiters.length = 0;
|
|
263
|
+
maxDelimiters.push(delim);
|
|
264
|
+
} else if (count === maxCount) {
|
|
265
|
+
maxDelimiters.push(delim);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (maxCount === 0 || maxDelimiters.length > 1) {
|
|
270
|
+
detectedDelimiter = ';';
|
|
271
|
+
} else {
|
|
272
|
+
detectedDelimiter = maxDelimiters[0];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (cache) {
|
|
276
|
+
cache.set(csv, candidates, detectedDelimiter);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return detectedDelimiter;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Создание глобального экземпляра кэша
|
|
284
|
+
let globalCache: DelimiterCache | null = null;
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Возвращает глобальный экземпляр кэша разделителей
|
|
288
|
+
*/
|
|
289
|
+
export function getGlobalDelimiterCache(maxSize: number = 100): DelimiterCache {
|
|
290
|
+
if (!globalCache) {
|
|
291
|
+
globalCache = new DelimiterCache(maxSize);
|
|
292
|
+
}
|
|
293
|
+
return globalCache;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Асинхронная версия getGlobalDelimiterCache
|
|
298
|
+
*/
|
|
299
|
+
export async function getGlobalDelimiterCacheAsync(maxSize: number = 100): Promise<DelimiterCache> {
|
|
300
|
+
return getGlobalDelimiterCache(maxSize);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Экспорт для CommonJS
|
|
304
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
305
|
+
module.exports = {
|
|
306
|
+
DelimiterCache,
|
|
307
|
+
getGlobalDelimiterCache,
|
|
308
|
+
getGlobalDelimiterCacheAsync
|
|
309
|
+
};
|
|
310
|
+
}
|