jtcsv 3.0.0 → 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.
- package/README.md +205 -146
- package/bin/jtcsv.ts +280 -202
- package/browser.d.ts +142 -0
- package/dist/benchmark.js +446 -0
- package/dist/benchmark.js.map +1 -0
- package/dist/bin/jtcsv.js +1940 -0
- package/dist/bin/jtcsv.js.map +1 -0
- package/dist/csv-to-json.js +1262 -0
- package/dist/csv-to-json.js.map +1 -0
- package/dist/errors.js +291 -0
- package/dist/errors.js.map +1 -0
- package/dist/eslint.config.js +147 -0
- package/dist/eslint.config.js.map +1 -0
- package/dist/index-core.js +95 -0
- package/dist/index-core.js.map +1 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/dist/json-save.js +229 -0
- package/dist/json-save.js.map +1 -0
- package/dist/json-to-csv.js +576 -0
- package/dist/json-to-csv.js.map +1 -0
- package/dist/jtcsv-core.cjs.js +336 -7
- package/dist/jtcsv-core.cjs.js.map +1 -1
- package/dist/jtcsv-core.esm.js +336 -7
- package/dist/jtcsv-core.esm.js.map +1 -1
- package/dist/jtcsv-core.umd.js +336 -7
- package/dist/jtcsv-core.umd.js.map +1 -1
- package/dist/jtcsv-full.cjs.js +336 -7
- package/dist/jtcsv-full.cjs.js.map +1 -1
- package/dist/jtcsv-full.esm.js +336 -7
- package/dist/jtcsv-full.esm.js.map +1 -1
- package/dist/jtcsv-full.umd.js +336 -7
- package/dist/jtcsv-full.umd.js.map +1 -1
- package/dist/jtcsv-workers.esm.js +9 -0
- package/dist/jtcsv-workers.esm.js.map +1 -1
- package/dist/jtcsv-workers.umd.js +9 -0
- package/dist/jtcsv-workers.umd.js.map +1 -1
- package/dist/jtcsv.cjs.js +1998 -2092
- package/dist/jtcsv.cjs.js.map +1 -1
- package/dist/jtcsv.esm.js +1994 -2092
- package/dist/jtcsv.esm.js.map +1 -1
- package/dist/jtcsv.umd.js +2157 -2251
- package/dist/jtcsv.umd.js.map +1 -1
- package/dist/plugins/express-middleware/index.js +350 -0
- package/dist/plugins/express-middleware/index.js.map +1 -0
- package/dist/plugins/fastify-plugin/index.js +315 -0
- package/dist/plugins/fastify-plugin/index.js.map +1 -0
- package/dist/plugins/hono/index.js +111 -0
- package/dist/plugins/hono/index.js.map +1 -0
- package/dist/plugins/nestjs/index.js +112 -0
- package/dist/plugins/nestjs/index.js.map +1 -0
- package/dist/plugins/nuxt/index.js +53 -0
- package/dist/plugins/nuxt/index.js.map +1 -0
- package/dist/plugins/remix/index.js +133 -0
- package/dist/plugins/remix/index.js.map +1 -0
- package/dist/plugins/sveltekit/index.js +155 -0
- package/dist/plugins/sveltekit/index.js.map +1 -0
- package/dist/plugins/trpc/index.js +136 -0
- package/dist/plugins/trpc/index.js.map +1 -0
- package/dist/run-demo.js +49 -0
- package/dist/run-demo.js.map +1 -0
- package/dist/src/browser/browser-functions.js +193 -0
- package/dist/src/browser/browser-functions.js.map +1 -0
- package/dist/src/browser/core.js +123 -0
- package/dist/src/browser/core.js.map +1 -0
- package/dist/src/browser/csv-to-json-browser.js +353 -0
- package/dist/src/browser/csv-to-json-browser.js.map +1 -0
- package/dist/src/browser/errors-browser.js +219 -0
- package/dist/src/browser/errors-browser.js.map +1 -0
- package/dist/src/browser/extensions/plugins.js +106 -0
- package/dist/src/browser/extensions/plugins.js.map +1 -0
- package/dist/src/browser/extensions/workers.js +66 -0
- package/dist/src/browser/extensions/workers.js.map +1 -0
- package/dist/src/browser/index.js +140 -0
- package/dist/src/browser/index.js.map +1 -0
- package/dist/src/browser/json-to-csv-browser.js +225 -0
- package/dist/src/browser/json-to-csv-browser.js.map +1 -0
- package/dist/src/browser/streams.js +340 -0
- package/dist/src/browser/streams.js.map +1 -0
- package/dist/src/browser/workers/csv-parser.worker.js +264 -0
- package/dist/src/browser/workers/csv-parser.worker.js.map +1 -0
- package/dist/src/browser/workers/worker-pool.js +338 -0
- package/dist/src/browser/workers/worker-pool.js.map +1 -0
- package/dist/src/core/delimiter-cache.js +196 -0
- package/dist/src/core/delimiter-cache.js.map +1 -0
- package/dist/src/core/node-optimizations.js +279 -0
- package/dist/src/core/node-optimizations.js.map +1 -0
- package/dist/src/core/plugin-system.js +399 -0
- package/dist/src/core/plugin-system.js.map +1 -0
- package/dist/src/core/transform-hooks.js +348 -0
- package/dist/src/core/transform-hooks.js.map +1 -0
- package/dist/src/engines/fast-path-engine-new.js +262 -0
- package/dist/src/engines/fast-path-engine-new.js.map +1 -0
- package/dist/src/engines/fast-path-engine.js +671 -0
- package/dist/src/engines/fast-path-engine.js.map +1 -0
- package/dist/src/errors.js +18 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/formats/ndjson-parser.js +332 -0
- package/dist/src/formats/ndjson-parser.js.map +1 -0
- package/dist/src/formats/tsv-parser.js +230 -0
- package/dist/src/formats/tsv-parser.js.map +1 -0
- package/dist/src/index-with-plugins.js +259 -0
- package/dist/src/index-with-plugins.js.map +1 -0
- package/dist/src/types/index.js +3 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/utils/bom-utils.js +267 -0
- package/dist/src/utils/bom-utils.js.map +1 -0
- package/dist/src/utils/encoding-support.js +77 -0
- package/dist/src/utils/encoding-support.js.map +1 -0
- package/dist/src/utils/schema-validator.js +609 -0
- package/dist/src/utils/schema-validator.js.map +1 -0
- package/dist/src/utils/transform-loader.js +281 -0
- package/dist/src/utils/transform-loader.js.map +1 -0
- package/dist/src/utils/validators.js +40 -0
- package/dist/src/utils/validators.js.map +1 -0
- package/dist/src/utils/zod-adapter.js +144 -0
- package/dist/src/utils/zod-adapter.js.map +1 -0
- package/{src → dist/src}/web-server/index.js +251 -286
- package/dist/src/web-server/index.js.map +1 -0
- package/dist/src/workers/csv-multithreaded.js +211 -0
- package/dist/src/workers/csv-multithreaded.js.map +1 -0
- package/dist/src/workers/csv-parser.worker.js +179 -0
- package/dist/src/workers/csv-parser.worker.js.map +1 -0
- package/dist/src/workers/worker-pool.js +228 -0
- package/dist/src/workers/worker-pool.js.map +1 -0
- package/dist/stream-csv-to-json.js +665 -0
- package/dist/stream-csv-to-json.js.map +1 -0
- package/dist/stream-json-to-csv.js +389 -0
- package/dist/stream-json-to-csv.js.map +1 -0
- package/examples/advanced/conditional-transformations.ts +2 -2
- package/examples/advanced/performance-optimization.ts +2 -2
- package/examples/cli-advanced-usage.md +2 -0
- package/examples/cli-tool.ts +1 -1
- package/examples/large-dataset-example.ts +2 -2
- package/examples/simple-usage.ts +2 -2
- package/examples/streaming-example.ts +1 -1
- package/index.d.ts +186 -15
- package/package.json +43 -108
- package/plugins.d.ts +37 -0
- package/schema.d.ts +103 -0
- package/src/browser/csv-to-json-browser.ts +233 -3
- package/src/browser/errors-browser.ts +45 -28
- package/src/browser/json-to-csv-browser.ts +81 -5
- package/src/browser/streams.ts +73 -6
- package/src/core/delimiter-cache.ts +21 -11
- package/src/core/plugin-system.ts +343 -155
- package/src/core/transform-hooks.ts +20 -12
- package/src/engines/fast-path-engine.ts +48 -32
- package/src/errors.ts +1 -72
- package/src/formats/ndjson-parser.ts +6 -0
- package/src/formats/tsv-parser.ts +6 -0
- package/src/types/index.ts +21 -1
- package/src/utils/validators.ts +35 -0
- package/src/web-server/index.ts +1 -1
- package/bin/jtcsv.js +0 -2532
- package/csv-to-json.js +0 -711
- package/errors.js +0 -394
- package/examples/advanced/conditional-transformations.js +0 -446
- package/examples/advanced/csv-parser.worker.js +0 -89
- package/examples/advanced/nested-objects-example.js +0 -306
- package/examples/advanced/performance-optimization.js +0 -504
- package/examples/advanced/run-demo-server.js +0 -116
- package/examples/cli-batch-processing.js +0 -38
- package/examples/cli-tool.js +0 -183
- package/examples/error-handling.js +0 -338
- package/examples/express-api.js +0 -164
- package/examples/large-dataset-example.js +0 -182
- package/examples/ndjson-processing.js +0 -434
- package/examples/plugin-excel-exporter.js +0 -406
- package/examples/schema-validation.js +0 -640
- package/examples/simple-usage.js +0 -282
- package/examples/streaming-example.js +0 -418
- package/examples/web-workers-advanced.js +0 -28
- package/index.js +0 -82
- package/json-save.js +0 -255
- package/json-to-csv.js +0 -668
- package/plugins/README.md +0 -91
- package/plugins/express-middleware/README.md +0 -83
- package/plugins/express-middleware/example.js +0 -135
- package/plugins/express-middleware/example.ts +0 -135
- package/plugins/express-middleware/index.d.ts +0 -114
- package/plugins/express-middleware/index.js +0 -512
- package/plugins/express-middleware/index.ts +0 -557
- package/plugins/express-middleware/package.json +0 -52
- package/plugins/fastify-plugin/index.js +0 -404
- package/plugins/fastify-plugin/index.ts +0 -443
- package/plugins/fastify-plugin/package.json +0 -55
- package/plugins/hono/README.md +0 -28
- package/plugins/hono/index.d.ts +0 -12
- package/plugins/hono/index.js +0 -36
- package/plugins/hono/index.ts +0 -226
- package/plugins/hono/package.json +0 -35
- package/plugins/nestjs/README.md +0 -35
- package/plugins/nestjs/index.d.ts +0 -25
- package/plugins/nestjs/index.js +0 -77
- package/plugins/nestjs/index.ts +0 -201
- package/plugins/nestjs/package.json +0 -37
- package/plugins/nextjs-api/README.md +0 -57
- package/plugins/nextjs-api/examples/ConverterComponent.jsx +0 -386
- package/plugins/nextjs-api/examples/ConverterComponent.tsx +0 -386
- package/plugins/nextjs-api/examples/api-convert.js +0 -67
- package/plugins/nextjs-api/examples/api-convert.ts +0 -67
- package/plugins/nextjs-api/index.js +0 -387
- package/plugins/nextjs-api/index.tsx +0 -339
- package/plugins/nextjs-api/package.json +0 -63
- package/plugins/nextjs-api/route.js +0 -370
- package/plugins/nextjs-api/route.ts +0 -370
- package/plugins/nuxt/README.md +0 -24
- package/plugins/nuxt/index.js +0 -21
- package/plugins/nuxt/index.ts +0 -94
- package/plugins/nuxt/package.json +0 -35
- package/plugins/nuxt/runtime/composables/useJtcsv.js +0 -6
- package/plugins/nuxt/runtime/composables/useJtcsv.ts +0 -100
- package/plugins/nuxt/runtime/plugin.js +0 -6
- package/plugins/nuxt/runtime/plugin.ts +0 -71
- package/plugins/remix/README.md +0 -26
- package/plugins/remix/index.d.ts +0 -16
- package/plugins/remix/index.js +0 -62
- package/plugins/remix/index.ts +0 -260
- package/plugins/remix/package.json +0 -35
- package/plugins/sveltekit/README.md +0 -28
- package/plugins/sveltekit/index.d.ts +0 -17
- package/plugins/sveltekit/index.js +0 -54
- package/plugins/sveltekit/index.ts +0 -301
- package/plugins/sveltekit/package.json +0 -33
- package/plugins/trpc/README.md +0 -25
- package/plugins/trpc/index.d.ts +0 -7
- package/plugins/trpc/index.js +0 -32
- package/plugins/trpc/index.ts +0 -267
- package/plugins/trpc/package.json +0 -34
- package/src/browser/browser-functions.js +0 -219
- package/src/browser/core.js +0 -92
- package/src/browser/csv-to-json-browser.js +0 -722
- package/src/browser/errors-browser.js +0 -212
- package/src/browser/extensions/plugins.js +0 -92
- package/src/browser/extensions/workers.js +0 -39
- package/src/browser/index.js +0 -113
- package/src/browser/json-to-csv-browser.js +0 -319
- package/src/browser/streams.js +0 -403
- package/src/browser/workers/csv-parser.worker.js +0 -377
- package/src/browser/workers/worker-pool.js +0 -527
- package/src/core/delimiter-cache.js +0 -200
- package/src/core/node-optimizations.js +0 -408
- package/src/core/plugin-system.js +0 -494
- package/src/core/transform-hooks.js +0 -350
- package/src/engines/fast-path-engine-new.js +0 -338
- package/src/engines/fast-path-engine.js +0 -844
- package/src/errors.js +0 -26
- package/src/formats/ndjson-parser.js +0 -467
- package/src/formats/tsv-parser.js +0 -339
- package/src/index-with-plugins.js +0 -378
- package/src/utils/bom-utils.js +0 -259
- package/src/utils/encoding-support.js +0 -124
- package/src/utils/schema-validator.js +0 -594
- package/src/utils/transform-loader.js +0 -205
- package/src/utils/zod-adapter.js +0 -170
- package/stream-csv-to-json.js +0 -560
- package/stream-json-to-csv.js +0 -465
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Transform Hooks System
|
|
3
|
-
* Система хуков для трансформации данных перед/после конвертации
|
|
4
|
-
*
|
|
5
|
-
* @version 1.0.0
|
|
6
|
-
* @date 2026-01-23
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const { ValidationError } = require('../../errors');
|
|
10
|
-
|
|
11
|
-
class TransformHooks {
|
|
12
|
-
constructor() {
|
|
13
|
-
this.hooks = {
|
|
14
|
-
beforeConvert: [],
|
|
15
|
-
afterConvert: [],
|
|
16
|
-
perRow: []
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Регистрирует хук beforeConvert
|
|
22
|
-
* @param {Function} hook - Функция хука
|
|
23
|
-
* @returns {TransformHooks} this для цепочки вызовов
|
|
24
|
-
*/
|
|
25
|
-
beforeConvert(hook) {
|
|
26
|
-
if (typeof hook !== 'function') {
|
|
27
|
-
throw new ValidationError('beforeConvert hook must be a function');
|
|
28
|
-
}
|
|
29
|
-
this.hooks.beforeConvert.push(hook);
|
|
30
|
-
return this;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Регистрирует хук afterConvert
|
|
35
|
-
* @param {Function} hook - Функция хука
|
|
36
|
-
* @returns {TransformHooks} this для цепочки вызовов
|
|
37
|
-
*/
|
|
38
|
-
afterConvert(hook) {
|
|
39
|
-
if (typeof hook !== 'function') {
|
|
40
|
-
throw new ValidationError('afterConvert hook must be a function');
|
|
41
|
-
}
|
|
42
|
-
this.hooks.afterConvert.push(hook);
|
|
43
|
-
return this;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Регистрирует per-row хук
|
|
48
|
-
* @param {Function} hook - Функция хука
|
|
49
|
-
* @returns {TransformHooks} this для цепочки вызовов
|
|
50
|
-
*/
|
|
51
|
-
perRow(hook) {
|
|
52
|
-
if (typeof hook !== 'function') {
|
|
53
|
-
throw new ValidationError('perRow hook must be a function');
|
|
54
|
-
}
|
|
55
|
-
this.hooks.perRow.push(hook);
|
|
56
|
-
return this;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Применяет beforeConvert хуки
|
|
61
|
-
* @param {any} data - Входные данные
|
|
62
|
-
* @param {Object} context - Контекст выполнения
|
|
63
|
-
* @returns {any} Трансформированные данные
|
|
64
|
-
*/
|
|
65
|
-
applyBeforeConvert(data, context = {}) {
|
|
66
|
-
let result = data;
|
|
67
|
-
for (const hook of this.hooks.beforeConvert) {
|
|
68
|
-
result = hook(result, context);
|
|
69
|
-
}
|
|
70
|
-
return result;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Применяет afterConvert хуки
|
|
75
|
-
* @param {any} data - Выходные данные
|
|
76
|
-
* @param {Object} context - Контекст выполнения
|
|
77
|
-
* @returns {any} Трансформированные данные
|
|
78
|
-
*/
|
|
79
|
-
applyAfterConvert(data, context = {}) {
|
|
80
|
-
let result = data;
|
|
81
|
-
for (const hook of this.hooks.afterConvert) {
|
|
82
|
-
result = hook(result, context);
|
|
83
|
-
}
|
|
84
|
-
return result;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Применяет per-row хуки
|
|
89
|
-
* @param {any} row - Строка данных
|
|
90
|
-
* @param {number} index - Индекс строки
|
|
91
|
-
* @param {Object} context - Контекст выполнения
|
|
92
|
-
* @returns {any} Трансформированная строка
|
|
93
|
-
*/
|
|
94
|
-
applyPerRow(row, index, context = {}) {
|
|
95
|
-
let result = row;
|
|
96
|
-
for (const hook of this.hooks.perRow) {
|
|
97
|
-
result = hook(result, index, context);
|
|
98
|
-
}
|
|
99
|
-
return result;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Применяет все хуки к массиву данных
|
|
104
|
-
* @param {Array} data - Массив данных
|
|
105
|
-
* @param {Object} context - Контекст выполнения
|
|
106
|
-
* @returns {Array} Трансформированный массив
|
|
107
|
-
*/
|
|
108
|
-
applyAll(data, context = {}) {
|
|
109
|
-
if (!Array.isArray(data)) {
|
|
110
|
-
throw new ValidationError('Data must be an array for applyAll');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Применяем beforeConvert хуки
|
|
114
|
-
let processedData = this.applyBeforeConvert(data, context);
|
|
115
|
-
|
|
116
|
-
// Применяем per-row хуки к каждой строке
|
|
117
|
-
if (this.hooks.perRow.length > 0) {
|
|
118
|
-
processedData = processedData.map((row, index) =>
|
|
119
|
-
this.applyPerRow(row, index, context)
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Применяем afterConvert хуки
|
|
124
|
-
return this.applyAfterConvert(processedData, context);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Создает копию системы хуков
|
|
129
|
-
* @returns {TransformHooks} Новая копия
|
|
130
|
-
*/
|
|
131
|
-
clone() {
|
|
132
|
-
const cloned = new TransformHooks();
|
|
133
|
-
cloned.hooks = {
|
|
134
|
-
beforeConvert: [...this.hooks.beforeConvert],
|
|
135
|
-
afterConvert: [...this.hooks.afterConvert],
|
|
136
|
-
perRow: [...this.hooks.perRow]
|
|
137
|
-
};
|
|
138
|
-
return cloned;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Очищает все хуки
|
|
143
|
-
*/
|
|
144
|
-
clear() {
|
|
145
|
-
this.hooks = {
|
|
146
|
-
beforeConvert: [],
|
|
147
|
-
afterConvert: [],
|
|
148
|
-
perRow: []
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Возвращает статистику по хукам
|
|
154
|
-
* @returns {Object} Статистика
|
|
155
|
-
*/
|
|
156
|
-
getStats() {
|
|
157
|
-
return {
|
|
158
|
-
beforeConvert: this.hooks.beforeConvert.length,
|
|
159
|
-
afterConvert: this.hooks.afterConvert.length,
|
|
160
|
-
perRow: this.hooks.perRow.length,
|
|
161
|
-
total: this.hooks.beforeConvert.length +
|
|
162
|
-
this.hooks.afterConvert.length +
|
|
163
|
-
this.hooks.perRow.length
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Предопределенные хуки
|
|
170
|
-
*/
|
|
171
|
-
const predefinedHooks = {
|
|
172
|
-
/**
|
|
173
|
-
* Хук для фильтрации данных
|
|
174
|
-
* @param {Function} predicate - Функция-предикат
|
|
175
|
-
* @returns {Function} Хук фильтрации
|
|
176
|
-
*/
|
|
177
|
-
filter(predicate) {
|
|
178
|
-
return (data) => {
|
|
179
|
-
if (Array.isArray(data)) {
|
|
180
|
-
return data.filter(predicate);
|
|
181
|
-
}
|
|
182
|
-
return data;
|
|
183
|
-
};
|
|
184
|
-
},
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Хук для маппинга данных
|
|
188
|
-
* @param {Function} mapper - Функция-маппер
|
|
189
|
-
* @returns {Function} Хук маппинга
|
|
190
|
-
*/
|
|
191
|
-
map(mapper) {
|
|
192
|
-
return (data) => {
|
|
193
|
-
if (Array.isArray(data)) {
|
|
194
|
-
return data.map(mapper);
|
|
195
|
-
}
|
|
196
|
-
return data;
|
|
197
|
-
};
|
|
198
|
-
},
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Хук для сортировки данных
|
|
202
|
-
* @param {Function} compareFn - Функция сравнения
|
|
203
|
-
* @returns {Function} Хук сортировки
|
|
204
|
-
*/
|
|
205
|
-
sort(compareFn) {
|
|
206
|
-
return (data) => {
|
|
207
|
-
if (Array.isArray(data)) {
|
|
208
|
-
return [...data].sort(compareFn);
|
|
209
|
-
}
|
|
210
|
-
return data;
|
|
211
|
-
};
|
|
212
|
-
},
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Хук для ограничения количества записей
|
|
216
|
-
* @param {number} limit - Максимальное количество записей
|
|
217
|
-
* @returns {Function} Хук ограничения
|
|
218
|
-
*/
|
|
219
|
-
limit(limit) {
|
|
220
|
-
return (data) => {
|
|
221
|
-
if (Array.isArray(data)) {
|
|
222
|
-
return data.slice(0, limit);
|
|
223
|
-
}
|
|
224
|
-
return data;
|
|
225
|
-
};
|
|
226
|
-
},
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Хук для добавления метаданных
|
|
230
|
-
* @param {Object} metadata - Метаданные для добавления
|
|
231
|
-
* @returns {Function} Хук добавления метаданных
|
|
232
|
-
*/
|
|
233
|
-
addMetadata(metadata) {
|
|
234
|
-
return (data, context) => {
|
|
235
|
-
if (Array.isArray(data)) {
|
|
236
|
-
return data.map(item => ({
|
|
237
|
-
...item,
|
|
238
|
-
_metadata: {
|
|
239
|
-
...metadata,
|
|
240
|
-
timestamp: new Date().toISOString(),
|
|
241
|
-
context
|
|
242
|
-
}
|
|
243
|
-
}));
|
|
244
|
-
}
|
|
245
|
-
return data;
|
|
246
|
-
};
|
|
247
|
-
},
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Хук для преобразования ключей
|
|
251
|
-
* @param {Function} keyTransformer - Функция преобразования ключей
|
|
252
|
-
* @returns {Function} Хук преобразования ключей
|
|
253
|
-
*/
|
|
254
|
-
transformKeys(keyTransformer) {
|
|
255
|
-
return (data) => {
|
|
256
|
-
if (Array.isArray(data)) {
|
|
257
|
-
return data.map(item => {
|
|
258
|
-
const transformed = {};
|
|
259
|
-
for (const [key, value] of Object.entries(item)) {
|
|
260
|
-
transformed[keyTransformer(key)] = value;
|
|
261
|
-
}
|
|
262
|
-
return transformed;
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
return data;
|
|
266
|
-
};
|
|
267
|
-
},
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Хук для преобразования значений
|
|
271
|
-
* @param {Function} valueTransformer - Функция преобразования значений
|
|
272
|
-
* @returns {Function} Хук преобразования значений
|
|
273
|
-
*/
|
|
274
|
-
transformValues(valueTransformer) {
|
|
275
|
-
return (data) => {
|
|
276
|
-
if (Array.isArray(data)) {
|
|
277
|
-
return data.map(item => {
|
|
278
|
-
const transformed = {};
|
|
279
|
-
for (const [key, value] of Object.entries(item)) {
|
|
280
|
-
transformed[key] = valueTransformer(value, key);
|
|
281
|
-
}
|
|
282
|
-
return transformed;
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
return data;
|
|
286
|
-
};
|
|
287
|
-
},
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Хук для валидации данных
|
|
291
|
-
* @param {Function} validator - Функция валидации
|
|
292
|
-
* @param {Function} onError - Обработчик ошибки
|
|
293
|
-
* @returns {Function} Хук валидации
|
|
294
|
-
*/
|
|
295
|
-
validate(validator, onError = console.error) {
|
|
296
|
-
return (data) => {
|
|
297
|
-
if (Array.isArray(data)) {
|
|
298
|
-
const validData = [];
|
|
299
|
-
const errors = [];
|
|
300
|
-
|
|
301
|
-
data.forEach((item, index) => {
|
|
302
|
-
try {
|
|
303
|
-
const isValid = validator(item, index);
|
|
304
|
-
if (isValid) {
|
|
305
|
-
validData.push(item);
|
|
306
|
-
} else {
|
|
307
|
-
errors.push({ index, item, reason: 'Validation failed' });
|
|
308
|
-
}
|
|
309
|
-
} catch (error) {
|
|
310
|
-
errors.push({ index, item, error: error.message });
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
if (errors.length > 0) {
|
|
315
|
-
onError('Validation errors:', errors);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return validData;
|
|
319
|
-
}
|
|
320
|
-
return data;
|
|
321
|
-
};
|
|
322
|
-
},
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Хук для дедупликации данных
|
|
326
|
-
* @param {Function} keySelector - Функция выбора ключа
|
|
327
|
-
* @returns {Function} Хук дедупликации
|
|
328
|
-
*/
|
|
329
|
-
deduplicate(keySelector = JSON.stringify) {
|
|
330
|
-
return (data) => {
|
|
331
|
-
if (Array.isArray(data)) {
|
|
332
|
-
const seen = new Set();
|
|
333
|
-
return data.filter(item => {
|
|
334
|
-
const key = keySelector(item);
|
|
335
|
-
if (seen.has(key)) {
|
|
336
|
-
return false;
|
|
337
|
-
}
|
|
338
|
-
seen.add(key);
|
|
339
|
-
return true;
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
return data;
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
module.exports = {
|
|
348
|
-
TransformHooks,
|
|
349
|
-
predefinedHooks
|
|
350
|
-
};
|
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fast-Path Engine для оптимизации CSV парсинга
|
|
3
|
-
* Автоматически выбирает оптимальный парсер на основе структуры CSV
|
|
4
|
-
*
|
|
5
|
-
* @version 1.0.0
|
|
6
|
-
* @date 2026-01-22
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
class FastPathEngine {
|
|
10
|
-
constructor() {
|
|
11
|
-
this.compilers = new Map();
|
|
12
|
-
this.stats = {
|
|
13
|
-
simpleParserCount: 0,
|
|
14
|
-
quoteAwareParserCount: 0,
|
|
15
|
-
standardParserCount: 0,
|
|
16
|
-
cacheHits: 0,
|
|
17
|
-
cacheMisses: 0
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Анализирует структуру CSV и определяет оптимальный парсер
|
|
23
|
-
*/
|
|
24
|
-
analyzeStructure(sample, options = {}) {
|
|
25
|
-
const delimiter = options.delimiter || this._detectDelimiter(sample);
|
|
26
|
-
const lines = sample.split('\n').slice(0, 10);
|
|
27
|
-
|
|
28
|
-
let hasQuotes = false;
|
|
29
|
-
let hasNewlinesInFields = false;
|
|
30
|
-
let hasEscapedQuotes = false;
|
|
31
|
-
let maxFields = 0;
|
|
32
|
-
let totalFields = 0;
|
|
33
|
-
|
|
34
|
-
for (const line of lines) {
|
|
35
|
-
if (line.includes('"')) {
|
|
36
|
-
hasQuotes = true;
|
|
37
|
-
if (line.includes('""')) {
|
|
38
|
-
hasEscapedQuotes = true;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const quoteCount = (line.match(/"/g) || []).length;
|
|
43
|
-
if (quoteCount % 2 !== 0) {
|
|
44
|
-
hasNewlinesInFields = true;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const fieldCount = line.split(delimiter).length;
|
|
48
|
-
totalFields += fieldCount;
|
|
49
|
-
if (fieldCount > maxFields) {
|
|
50
|
-
maxFields = fieldCount;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const avgFieldsPerLine = totalFields / lines.length;
|
|
55
|
-
const fieldConsistency = maxFields === avgFieldsPerLine;
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
delimiter,
|
|
59
|
-
hasQuotes,
|
|
60
|
-
hasEscapedQuotes,
|
|
61
|
-
hasNewlinesInFields,
|
|
62
|
-
fieldConsistency,
|
|
63
|
-
avgFieldsPerLine,
|
|
64
|
-
maxFields,
|
|
65
|
-
recommendedEngine: this._selectEngine(hasQuotes, hasNewlinesInFields, fieldConsistency)
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Автоматически определяет разделитель
|
|
71
|
-
*/
|
|
72
|
-
_detectDelimiter(sample) {
|
|
73
|
-
const candidates = [',', ';', '\t', '|'];
|
|
74
|
-
const firstLine = sample.split('\n')[0];
|
|
75
|
-
|
|
76
|
-
let bestDelimiter = ',';
|
|
77
|
-
let bestScore = 0;
|
|
78
|
-
|
|
79
|
-
for (const delimiter of candidates) {
|
|
80
|
-
const fields = firstLine.split(delimiter);
|
|
81
|
-
const score = fields.length;
|
|
82
|
-
|
|
83
|
-
const avgLength = fields.reduce((sum, field) => sum + field.length, 0) / fields.length;
|
|
84
|
-
const variance = fields.reduce((sum, field) => sum + Math.pow(field.length - avgLength, 2), 0) / fields.length;
|
|
85
|
-
|
|
86
|
-
const finalScore = score / (variance + 1);
|
|
87
|
-
|
|
88
|
-
if (finalScore > bestScore) {
|
|
89
|
-
bestScore = finalScore;
|
|
90
|
-
bestDelimiter = delimiter;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return bestDelimiter;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Выбирает оптимальный движок парсинга
|
|
99
|
-
*/
|
|
100
|
-
_selectEngine(hasQuotes, hasNewlinesInFields, fieldConsistency) {
|
|
101
|
-
if (!hasQuotes && !hasNewlinesInFields && fieldConsistency) {
|
|
102
|
-
return 'SIMPLE';
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (hasQuotes && !hasNewlinesInFields) {
|
|
106
|
-
return 'QUOTE_AWARE';
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return 'STANDARD';
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Компилирует специализированный парсер для конкретной структуры
|
|
114
|
-
*/
|
|
115
|
-
compileParser(structure) {
|
|
116
|
-
const cacheKey = JSON.stringify(structure);
|
|
117
|
-
|
|
118
|
-
if (this.compilers.has(cacheKey)) {
|
|
119
|
-
this.stats.cacheHits++;
|
|
120
|
-
return this.compilers.get(cacheKey);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
this.stats.cacheMisses++;
|
|
124
|
-
let parser;
|
|
125
|
-
|
|
126
|
-
switch (structure.recommendedEngine) {
|
|
127
|
-
case 'SIMPLE':
|
|
128
|
-
parser = this._createSimpleParser(structure);
|
|
129
|
-
this.stats.simpleParserCount++;
|
|
130
|
-
break;
|
|
131
|
-
case 'QUOTE_AWARE':
|
|
132
|
-
parser = this._createQuoteAwareParser(structure);
|
|
133
|
-
this.stats.quoteAwareParserCount++;
|
|
134
|
-
break;
|
|
135
|
-
default:
|
|
136
|
-
parser = this._createStandardParser(structure);
|
|
137
|
-
this.stats.standardParserCount++;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
this.compilers.set(cacheKey, parser);
|
|
141
|
-
return parser;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Regex-based парсер для простых CSV (без кавычек)
|
|
146
|
-
*/
|
|
147
|
-
_createSimpleParser(structure) {
|
|
148
|
-
const { delimiter } = structure;
|
|
149
|
-
|
|
150
|
-
return (csv) => {
|
|
151
|
-
const rows = [];
|
|
152
|
-
const lines = csv.split('\n');
|
|
153
|
-
|
|
154
|
-
for (const line of lines) {
|
|
155
|
-
if (line.trim() === '') {
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const fields = line.split(delimiter).map(field => field.trim());
|
|
160
|
-
if (fields.length > 0 && fields.some(field => field !== '')) {
|
|
161
|
-
rows.push(fields);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return rows;
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* State machine парсер для CSV с кавычками (RFC 4180)
|
|
171
|
-
*/
|
|
172
|
-
_createQuoteAwareParser(structure) {
|
|
173
|
-
const { delimiter, hasEscapedQuotes } = structure;
|
|
174
|
-
|
|
175
|
-
return (csv) => {
|
|
176
|
-
const rows = [];
|
|
177
|
-
let currentRow = [];
|
|
178
|
-
let currentField = '';
|
|
179
|
-
let insideQuotes = false;
|
|
180
|
-
let i = 0;
|
|
181
|
-
|
|
182
|
-
while (i < csv.length) {
|
|
183
|
-
const char = csv[i];
|
|
184
|
-
const nextChar = csv[i + 1];
|
|
185
|
-
|
|
186
|
-
if (char === '"') {
|
|
187
|
-
if (insideQuotes) {
|
|
188
|
-
if (nextChar === '"' && hasEscapedQuotes) {
|
|
189
|
-
currentField += '"';
|
|
190
|
-
i += 2;
|
|
191
|
-
} else {
|
|
192
|
-
insideQuotes = false;
|
|
193
|
-
i++;
|
|
194
|
-
}
|
|
195
|
-
} else {
|
|
196
|
-
insideQuotes = true;
|
|
197
|
-
i++;
|
|
198
|
-
}
|
|
199
|
-
} else if (char === delimiter && !insideQuotes) {
|
|
200
|
-
currentRow.push(currentField);
|
|
201
|
-
currentField = '';
|
|
202
|
-
i++;
|
|
203
|
-
} else if ((char === '\n' || (char === '\r' && nextChar === '\n')) && !insideQuotes) {
|
|
204
|
-
currentRow.push(currentField);
|
|
205
|
-
if (currentRow.length > 0 && currentRow.some(field => field !== '')) {
|
|
206
|
-
rows.push(currentRow);
|
|
207
|
-
}
|
|
208
|
-
currentRow = [];
|
|
209
|
-
currentField = '';
|
|
210
|
-
i += (char === '\r' && nextChar === '\n') ? 2 : 1;
|
|
211
|
-
} else {
|
|
212
|
-
currentField += char;
|
|
213
|
-
i++;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (currentField !== '' || currentRow.length > 0) {
|
|
218
|
-
currentRow.push(currentField);
|
|
219
|
-
if (currentRow.length > 0 && currentRow.some(field => field !== '')) {
|
|
220
|
-
rows.push(currentRow);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return rows;
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Стандартный парсер (fallback)
|
|
230
|
-
*/
|
|
231
|
-
_createStandardParser(structure) {
|
|
232
|
-
const { delimiter } = structure;
|
|
233
|
-
|
|
234
|
-
return (csv) => {
|
|
235
|
-
const rows = [];
|
|
236
|
-
const lines = csv.split('\n');
|
|
237
|
-
let insideQuotes = false;
|
|
238
|
-
let currentLine = '';
|
|
239
|
-
|
|
240
|
-
for (const line of lines) {
|
|
241
|
-
const quoteCount = (line.match(/"/g) || []).length;
|
|
242
|
-
|
|
243
|
-
if (insideQuotes) {
|
|
244
|
-
currentLine += '\n' + line;
|
|
245
|
-
if (quoteCount % 2 !== 0) {
|
|
246
|
-
insideQuotes = false;
|
|
247
|
-
rows.push(this._parseLineWithQuotes(currentLine, delimiter));
|
|
248
|
-
currentLine = '';
|
|
249
|
-
}
|
|
250
|
-
} else {
|
|
251
|
-
if (quoteCount % 2 !== 0) {
|
|
252
|
-
insideQuotes = true;
|
|
253
|
-
currentLine = line;
|
|
254
|
-
} else {
|
|
255
|
-
rows.push(this._parseLineWithQuotes(line, delimiter));
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return rows;
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Парсит строку с учетом кавычек
|
|
266
|
-
*/
|
|
267
|
-
_parseLineWithQuotes(line, delimiter) {
|
|
268
|
-
const fields = [];
|
|
269
|
-
let currentField = '';
|
|
270
|
-
let insideQuotes = false;
|
|
271
|
-
let i = 0;
|
|
272
|
-
|
|
273
|
-
while (i < line.length) {
|
|
274
|
-
const char = line[i];
|
|
275
|
-
const nextChar = line[i + 1];
|
|
276
|
-
|
|
277
|
-
if (char === '"') {
|
|
278
|
-
if (insideQuotes && nextChar === '"') {
|
|
279
|
-
currentField += '"';
|
|
280
|
-
i += 2;
|
|
281
|
-
} else {
|
|
282
|
-
insideQuotes = !insideQuotes;
|
|
283
|
-
i++;
|
|
284
|
-
}
|
|
285
|
-
} else if (char === delimiter && !insideQuotes) {
|
|
286
|
-
fields.push(currentField);
|
|
287
|
-
currentField = '';
|
|
288
|
-
i++;
|
|
289
|
-
} else {
|
|
290
|
-
currentField += char;
|
|
291
|
-
i++;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
fields.push(currentField);
|
|
296
|
-
return fields;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Парсит CSV с использованием оптимального парсера
|
|
301
|
-
*/
|
|
302
|
-
parse(csv, options = {}) {
|
|
303
|
-
const sampleSize = Math.min(1000, csv.length);
|
|
304
|
-
const sample = csv.substring(0, sampleSize);
|
|
305
|
-
|
|
306
|
-
const structure = this.analyzeStructure(sample, options);
|
|
307
|
-
const parser = this.compileParser(structure);
|
|
308
|
-
|
|
309
|
-
return parser(csv);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Возвращает статистику использования парсеров
|
|
314
|
-
*/
|
|
315
|
-
getStats() {
|
|
316
|
-
return {
|
|
317
|
-
...this.stats,
|
|
318
|
-
totalParsers: this.compilers.size,
|
|
319
|
-
hitRate: this.stats.cacheHits / (this.stats.cacheHits + this.stats.cacheMisses) || 0
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Сбрасывает статистику и кеш
|
|
325
|
-
*/
|
|
326
|
-
reset() {
|
|
327
|
-
this.compilers.clear();
|
|
328
|
-
this.stats = {
|
|
329
|
-
simpleParserCount: 0,
|
|
330
|
-
quoteAwareParserCount: 0,
|
|
331
|
-
standardParserCount: 0,
|
|
332
|
-
cacheHits: 0,
|
|
333
|
-
cacheMisses: 0
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
module.exports = FastPathEngine;
|