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.
- package/README.md +204 -115
- package/bin/jtcsv.ts +2612 -0
- 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 +1736 -0
- package/dist/jtcsv-core.cjs.js.map +1 -0
- package/dist/jtcsv-core.esm.js +1708 -0
- package/dist/jtcsv-core.esm.js.map +1 -0
- package/dist/jtcsv-core.umd.js +1742 -0
- package/dist/jtcsv-core.umd.js.map +1 -0
- package/dist/jtcsv-full.cjs.js +2241 -0
- package/dist/jtcsv-full.cjs.js.map +1 -0
- package/dist/jtcsv-full.esm.js +2209 -0
- package/dist/jtcsv-full.esm.js.map +1 -0
- package/dist/jtcsv-full.umd.js +2247 -0
- package/dist/jtcsv-full.umd.js.map +1 -0
- package/dist/jtcsv-workers.esm.js +768 -0
- package/dist/jtcsv-workers.esm.js.map +1 -0
- package/dist/jtcsv-workers.umd.js +782 -0
- package/dist/jtcsv-workers.umd.js.map +1 -0
- package/dist/jtcsv.cjs.js +1996 -2048
- package/dist/jtcsv.cjs.js.map +1 -1
- package/dist/jtcsv.esm.js +1992 -2048
- package/dist/jtcsv.esm.js.map +1 -1
- package/dist/jtcsv.umd.js +2157 -2209
- 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/dist/src/web-server/index.js +648 -0
- 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 +446 -0
- package/examples/advanced/csv-parser.worker.ts +89 -0
- package/examples/advanced/nested-objects-example.ts +306 -0
- package/examples/advanced/performance-optimization.ts +504 -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 +290 -0
- package/examples/{cli-batch-processing.js → cli-batch-processing.ts} +38 -38
- package/examples/{cli-tool.js → cli-tool.ts} +5 -8
- package/examples/{error-handling.js → error-handling.ts} +356 -324
- package/examples/{express-api.js → express-api.ts} +161 -164
- package/examples/{large-dataset-example.js → large-dataset-example.ts} +201 -182
- package/examples/{ndjson-processing.js → ndjson-processing.ts} +456 -434
- package/examples/{plugin-excel-exporter.js → plugin-excel-exporter.ts} +6 -7
- package/examples/react-integration.tsx +637 -0
- package/examples/{schema-validation.js → schema-validation.ts} +2 -2
- package/examples/simple-usage.ts +194 -0
- package/examples/{streaming-example.js → streaming-example.ts} +12 -12
- package/index.d.ts +187 -18
- package/package.json +75 -81
- package/plugins.d.ts +37 -0
- package/schema.d.ts +103 -0
- package/src/browser/browser-functions.ts +402 -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.ts +494 -0
- package/src/browser/{errors-browser.js → errors-browser.ts} +305 -197
- package/src/browser/extensions/plugins.ts +93 -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.ts +338 -0
- package/src/browser/streams.ts +403 -0
- package/src/browser/workers/{csv-parser.worker.js → csv-parser.worker.ts} +3 -3
- package/src/browser/workers/{worker-pool.js → worker-pool.ts} +51 -30
- package/src/core/delimiter-cache.ts +320 -0
- package/src/core/{node-optimizations.js → node-optimizations.ts} +448 -407
- package/src/core/plugin-system.ts +588 -0
- package/src/core/transform-hooks.ts +566 -0
- package/src/engines/{fast-path-engine-new.js → fast-path-engine-new.ts} +11 -2
- package/src/engines/{fast-path-engine.js → fast-path-engine.ts} +79 -53
- package/src/errors.ts +1 -0
- package/src/formats/{ndjson-parser.js → ndjson-parser.ts} +24 -16
- package/src/formats/{tsv-parser.js → tsv-parser.ts} +18 -17
- package/src/{index-with-plugins.js → index-with-plugins.ts} +381 -357
- package/src/types/index.ts +275 -0
- package/src/utils/bom-utils.ts +373 -0
- package/src/utils/encoding-support.ts +155 -0
- package/src/utils/{schema-validator.js → schema-validator.ts} +814 -589
- package/src/utils/transform-loader.ts +389 -0
- package/src/utils/validators.ts +35 -0
- package/src/utils/zod-adapter.ts +280 -0
- package/src/web-server/{index.js → index.ts} +19 -19
- 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/bin/jtcsv.js +0 -2462
- package/csv-to-json.js +0 -688
- package/errors.js +0 -208
- package/examples/simple-usage.js +0 -282
- package/index.js +0 -68
- package/json-save.js +0 -254
- package/json-to-csv.js +0 -526
- package/plugins/README.md +0 -91
- package/plugins/express-middleware/README.md +0 -64
- package/plugins/express-middleware/example.js +0 -136
- package/plugins/express-middleware/index.d.ts +0 -114
- package/plugins/express-middleware/index.js +0 -360
- package/plugins/express-middleware/package.json +0 -52
- package/plugins/fastify-plugin/index.js +0 -406
- 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/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/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/api-convert.js +0 -69
- package/plugins/nextjs-api/index.js +0 -387
- package/plugins/nextjs-api/package.json +0 -63
- package/plugins/nextjs-api/route.js +0 -371
- package/plugins/nuxt/README.md +0 -24
- package/plugins/nuxt/index.js +0 -21
- package/plugins/nuxt/package.json +0 -35
- package/plugins/nuxt/runtime/composables/useJtcsv.js +0 -6
- package/plugins/nuxt/runtime/plugin.js +0 -6
- 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/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/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/package.json +0 -34
- package/src/browser/browser-functions.js +0 -219
- package/src/browser/csv-to-json-browser.js +0 -700
- package/src/browser/index.js +0 -113
- package/src/browser/json-to-csv-browser.js +0 -309
- package/src/browser/streams.js +0 -393
- package/src/core/delimiter-cache.js +0 -186
- package/src/core/plugin-system.js +0 -476
- package/src/core/transform-hooks.js +0 -350
- package/src/errors.js +0 -26
- package/src/utils/transform-loader.js +0 -205
- package/stream-csv-to-json.js +0 -542
- package/stream-json-to-csv.js +0 -464
- /package/examples/{web-workers-advanced.js → web-workers-advanced.ts} +0 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ValidationError,
|
|
3
|
+
ConfigurationError,
|
|
4
|
+
LimitError
|
|
5
|
+
} from './errors-browser';
|
|
6
|
+
import { csvToJsonIterator } from './csv-to-json-browser';
|
|
7
|
+
|
|
8
|
+
import type { CsvToJsonOptions, JsonToCsvOptions } from '../types';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_MAX_CHUNK_SIZE = 64 * 1024;
|
|
11
|
+
const PHONE_KEYS = new Set(['phone', 'phonenumber', 'phone_number', 'tel', 'telephone']);
|
|
12
|
+
|
|
13
|
+
function isReadableStream(value: any): value is ReadableStream {
|
|
14
|
+
return value && typeof value.getReader === 'function';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isAsyncIterable(value: any): value is AsyncIterable<any> {
|
|
18
|
+
return value && typeof value[Symbol.asyncIterator] === 'function';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isIterable(value: any): value is Iterable<any> {
|
|
22
|
+
return value && typeof value[Symbol.iterator] === 'function';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function createReadableStreamFromIterator<T>(iterator: AsyncIterator<T>): ReadableStream<T> {
|
|
26
|
+
return new ReadableStream({
|
|
27
|
+
async pull(controller) {
|
|
28
|
+
try {
|
|
29
|
+
const { value, done } = await iterator.next();
|
|
30
|
+
if (done) {
|
|
31
|
+
controller.close();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
controller.enqueue(value);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
controller.error(error);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
cancel() {
|
|
40
|
+
if (iterator.return) {
|
|
41
|
+
iterator.return();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function detectInputFormat(input: any, options: any): 'json' | 'ndjson' | 'csv' | 'unknown' {
|
|
48
|
+
if (options && options.inputFormat) {
|
|
49
|
+
return options.inputFormat;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (typeof input === 'string') {
|
|
53
|
+
const trimmed = input.trim();
|
|
54
|
+
if (trimmed === '') {
|
|
55
|
+
return 'unknown';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Проверка на NDJSON (каждая строка - валидный JSON)
|
|
59
|
+
if (trimmed.includes('\n')) {
|
|
60
|
+
const lines = trimmed.split('\n').filter(line => line.trim() !== '');
|
|
61
|
+
if (lines.length > 0) {
|
|
62
|
+
try {
|
|
63
|
+
JSON.parse(lines[0]);
|
|
64
|
+
return 'ndjson';
|
|
65
|
+
} catch {
|
|
66
|
+
// Не NDJSON
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Проверка на JSON
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(trimmed);
|
|
74
|
+
if (Array.isArray(parsed) || (parsed && typeof parsed === 'object')) {
|
|
75
|
+
return 'json';
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
// Не JSON
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Проверка на CSV
|
|
82
|
+
if (trimmed.includes(',') || trimmed.includes(';') || trimmed.includes('\t')) {
|
|
83
|
+
return 'csv';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return 'unknown';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function normalizeQuotesInField(value: string): string {
|
|
91
|
+
// Не нормализуем кавычки в JSON-строках - это ломает структуру JSON
|
|
92
|
+
// Проверяем, выглядит ли значение как JSON (объект или массив)
|
|
93
|
+
if ((value.startsWith('{') && value.endsWith('}')) ||
|
|
94
|
+
(value.startsWith('[') && value.endsWith(']'))) {
|
|
95
|
+
return value; // Возвращаем как есть для JSON
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let normalized = value.replace(/"{2,}/g, '"');
|
|
99
|
+
// Убираем правило, которое ломает JSON: не заменяем "," на ","
|
|
100
|
+
// normalized = normalized.replace(/"\s*,\s*"/g, ',');
|
|
101
|
+
normalized = normalized.replace(/"\n/g, '\n').replace(/\n"/g, '\n');
|
|
102
|
+
if (normalized.length >= 2 && normalized.startsWith('"') && normalized.endsWith('"')) {
|
|
103
|
+
normalized = normalized.slice(1, -1);
|
|
104
|
+
}
|
|
105
|
+
return normalized;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function normalizePhoneValue(value: string): string {
|
|
109
|
+
const trimmed = value.trim();
|
|
110
|
+
if (trimmed === '') {
|
|
111
|
+
return trimmed;
|
|
112
|
+
}
|
|
113
|
+
return trimmed.replace(/["'\\]/g, '');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function normalizeValueForCsv(value: any, key: string | undefined, normalizeQuotes: boolean): any {
|
|
117
|
+
if (!normalizeQuotes || typeof value !== 'string') {
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
const base = normalizeQuotesInField(value);
|
|
121
|
+
if (key && PHONE_KEYS.has(String(key).toLowerCase())) {
|
|
122
|
+
return normalizePhoneValue(base);
|
|
123
|
+
}
|
|
124
|
+
return base;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function* jsonToCsvChunkIterator(input: any, options: JsonToCsvOptions = {}): AsyncGenerator<string> {
|
|
128
|
+
const format = detectInputFormat(input, options);
|
|
129
|
+
|
|
130
|
+
if (format === 'csv') {
|
|
131
|
+
throw new ValidationError('Input appears to be CSV, not JSON');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Вспомогательная функция для создания асинхронного итератора
|
|
135
|
+
function toAsyncIterator<T>(iterable: Iterable<T> | AsyncIterable<T>): AsyncIterator<T> {
|
|
136
|
+
if (isAsyncIterable(iterable)) {
|
|
137
|
+
return iterable[Symbol.asyncIterator]();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (isIterable(iterable)) {
|
|
141
|
+
const syncIterator = iterable[Symbol.iterator]();
|
|
142
|
+
return {
|
|
143
|
+
next: () => Promise.resolve(syncIterator.next()),
|
|
144
|
+
return: syncIterator.return ? () => Promise.resolve(syncIterator.return!()) : undefined,
|
|
145
|
+
throw: syncIterator.throw ? (error: any) => Promise.resolve(syncIterator.throw!(error)) : undefined
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
throw new ValidationError('Input is not iterable');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let iterator: AsyncIterator<any>;
|
|
153
|
+
|
|
154
|
+
if (isAsyncIterable(input) || isIterable(input)) {
|
|
155
|
+
iterator = toAsyncIterator(input);
|
|
156
|
+
} else if (typeof input === 'string') {
|
|
157
|
+
const parsed = JSON.parse(input);
|
|
158
|
+
if (Array.isArray(parsed)) {
|
|
159
|
+
iterator = toAsyncIterator(parsed);
|
|
160
|
+
} else {
|
|
161
|
+
iterator = toAsyncIterator([parsed]);
|
|
162
|
+
}
|
|
163
|
+
} else if (Array.isArray(input)) {
|
|
164
|
+
iterator = toAsyncIterator(input);
|
|
165
|
+
} else {
|
|
166
|
+
iterator = toAsyncIterator([input]);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const delimiter = options.delimiter || ';';
|
|
170
|
+
const includeHeaders = options.includeHeaders !== false;
|
|
171
|
+
const preventInjection = options.preventCsvInjection !== false;
|
|
172
|
+
const normalizeQuotes = options.normalizeQuotes !== false;
|
|
173
|
+
const isPotentialFormula = (input: string): boolean => {
|
|
174
|
+
let idx = 0;
|
|
175
|
+
while (idx < input.length) {
|
|
176
|
+
const code = input.charCodeAt(idx);
|
|
177
|
+
if (code === 32 || code === 9 || code === 10 || code === 13 || code === 0xfeff) {
|
|
178
|
+
idx++;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
if (idx < input.length && (input[idx] === '"' || input[idx] === "'")) {
|
|
184
|
+
idx++;
|
|
185
|
+
while (idx < input.length) {
|
|
186
|
+
const code = input.charCodeAt(idx);
|
|
187
|
+
if (code === 32 || code === 9) {
|
|
188
|
+
idx++;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (idx >= input.length) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
const char = input[idx];
|
|
198
|
+
return char === '=' || char === '+' || char === '-' || char === '@';
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
let isFirstChunk = true;
|
|
202
|
+
let headers: string[] = [];
|
|
203
|
+
|
|
204
|
+
while (true) {
|
|
205
|
+
const { value, done } = await iterator.next();
|
|
206
|
+
if (done) break;
|
|
207
|
+
|
|
208
|
+
const item = value;
|
|
209
|
+
|
|
210
|
+
if (isFirstChunk) {
|
|
211
|
+
// Извлечение заголовков из первого элемента
|
|
212
|
+
headers = Object.keys(item);
|
|
213
|
+
|
|
214
|
+
if (includeHeaders) {
|
|
215
|
+
const headerLine = headers.map(header => {
|
|
216
|
+
const escaped = header.includes('"') ? `"${header.replace(/"/g, '""')}"` : header;
|
|
217
|
+
return preventInjection && isPotentialFormula(escaped) ? `'${escaped}` : escaped;
|
|
218
|
+
}).join(delimiter);
|
|
219
|
+
|
|
220
|
+
yield headerLine + '\n';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
isFirstChunk = false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const row = headers.map(header => {
|
|
227
|
+
const value = item[header];
|
|
228
|
+
const normalized = normalizeValueForCsv(value, header, normalizeQuotes);
|
|
229
|
+
const strValue = normalized === null || normalized === undefined ? '' : String(normalized);
|
|
230
|
+
|
|
231
|
+
if (strValue.includes('"') || strValue.includes('\n') || strValue.includes('\r') || strValue.includes(delimiter)) {
|
|
232
|
+
return `"${strValue.replace(/"/g, '""')}"`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (preventInjection && isPotentialFormula(strValue)) {
|
|
236
|
+
return `'${strValue}`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return strValue;
|
|
240
|
+
}).join(delimiter);
|
|
241
|
+
|
|
242
|
+
yield row + '\n';
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function* jsonToNdjsonChunkIterator(input: any, options: any = {}): AsyncGenerator<string> {
|
|
247
|
+
const format = detectInputFormat(input, options);
|
|
248
|
+
|
|
249
|
+
// Вспомогательная функция для создания асинхронного итератора
|
|
250
|
+
function toAsyncIterator<T>(iterable: Iterable<T> | AsyncIterable<T>): AsyncIterator<T> {
|
|
251
|
+
if (isAsyncIterable(iterable)) {
|
|
252
|
+
return iterable[Symbol.asyncIterator]();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (isIterable(iterable)) {
|
|
256
|
+
const syncIterator = iterable[Symbol.iterator]();
|
|
257
|
+
return {
|
|
258
|
+
next: () => Promise.resolve(syncIterator.next()),
|
|
259
|
+
return: syncIterator.return ? () => Promise.resolve(syncIterator.return!()) : undefined,
|
|
260
|
+
throw: syncIterator.throw ? (error: any) => Promise.resolve(syncIterator.throw!(error)) : undefined
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
throw new ValidationError('Input is not iterable');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let iterator: AsyncIterator<any>;
|
|
268
|
+
|
|
269
|
+
if (isAsyncIterable(input) || isIterable(input)) {
|
|
270
|
+
iterator = toAsyncIterator(input);
|
|
271
|
+
} else if (typeof input === 'string') {
|
|
272
|
+
if (format === 'ndjson') {
|
|
273
|
+
const lines = input.split('\n').filter(line => line.trim() !== '');
|
|
274
|
+
iterator = toAsyncIterator(lines);
|
|
275
|
+
} else {
|
|
276
|
+
const parsed = JSON.parse(input);
|
|
277
|
+
if (Array.isArray(parsed)) {
|
|
278
|
+
iterator = toAsyncIterator(parsed);
|
|
279
|
+
} else {
|
|
280
|
+
iterator = toAsyncIterator([parsed]);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
} else if (Array.isArray(input)) {
|
|
284
|
+
iterator = toAsyncIterator(input);
|
|
285
|
+
} else {
|
|
286
|
+
iterator = toAsyncIterator([input]);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
while (true) {
|
|
290
|
+
const { value, done } = await iterator.next();
|
|
291
|
+
if (done) break;
|
|
292
|
+
|
|
293
|
+
let jsonStr: string;
|
|
294
|
+
|
|
295
|
+
if (typeof value === 'string') {
|
|
296
|
+
try {
|
|
297
|
+
// Проверяем, является ли строка валидным JSON
|
|
298
|
+
JSON.parse(value);
|
|
299
|
+
jsonStr = value;
|
|
300
|
+
} catch {
|
|
301
|
+
// Если нет, сериализуем как JSON
|
|
302
|
+
jsonStr = JSON.stringify(value);
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
jsonStr = JSON.stringify(value);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
yield jsonStr + '\n';
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function* csvToJsonChunkIterator(input: any, options: CsvToJsonOptions = {}): AsyncGenerator<any> {
|
|
313
|
+
if (typeof input === 'string') {
|
|
314
|
+
// Используем csvToJsonIterator из csv-to-json-browser
|
|
315
|
+
yield* csvToJsonIterator(input, options);
|
|
316
|
+
} else if (input instanceof File || input instanceof Blob) {
|
|
317
|
+
const text = await input.text();
|
|
318
|
+
yield* csvToJsonIterator(text, options);
|
|
319
|
+
} else if (isReadableStream(input)) {
|
|
320
|
+
const reader = input.getReader();
|
|
321
|
+
const decoder = new TextDecoder();
|
|
322
|
+
let buffer = '';
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
while (true) {
|
|
326
|
+
const { value, done } = await reader.read();
|
|
327
|
+
if (done) break;
|
|
328
|
+
|
|
329
|
+
buffer += decoder.decode(value, { stream: true });
|
|
330
|
+
|
|
331
|
+
// Обработка буфера по строкам
|
|
332
|
+
const lines = buffer.split('\n');
|
|
333
|
+
buffer = lines.pop() || '';
|
|
334
|
+
|
|
335
|
+
// TODO: Реализовать парсинг CSV из чанков
|
|
336
|
+
// Пока просто возвращаем сырые строки
|
|
337
|
+
for (const line of lines) {
|
|
338
|
+
if (line.trim()) {
|
|
339
|
+
yield { raw: line };
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Обработка остатка буфера
|
|
345
|
+
if (buffer.trim()) {
|
|
346
|
+
yield { raw: buffer };
|
|
347
|
+
}
|
|
348
|
+
} finally {
|
|
349
|
+
reader.releaseLock();
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
throw new ValidationError('Unsupported input type for CSV streaming');
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function jsonToCsvStream(input: any, options: JsonToCsvOptions = {}): ReadableStream<string> {
|
|
357
|
+
const iterator = jsonToCsvChunkIterator(input, options);
|
|
358
|
+
return createReadableStreamFromIterator(iterator);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export function jsonToNdjsonStream(input: any, options: any = {}): ReadableStream<string> {
|
|
362
|
+
const iterator = jsonToNdjsonChunkIterator(input, options);
|
|
363
|
+
return createReadableStreamFromIterator(iterator);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function csvToJsonStream(input: any, options: CsvToJsonOptions = {}): ReadableStream<any> {
|
|
367
|
+
const iterator = csvToJsonChunkIterator(input, options);
|
|
368
|
+
return createReadableStreamFromIterator(iterator);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Асинхронная версия jsonToCsvStream
|
|
373
|
+
*/
|
|
374
|
+
export async function jsonToCsvStreamAsync(input: any, options: JsonToCsvOptions = {}): Promise<ReadableStream<string>> {
|
|
375
|
+
return jsonToCsvStream(input, options);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Асинхронная версия jsonToNdjsonStream
|
|
380
|
+
*/
|
|
381
|
+
export async function jsonToNdjsonStreamAsync(input: any, options: any = {}): Promise<ReadableStream<string>> {
|
|
382
|
+
return jsonToNdjsonStream(input, options);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Асинхронная версия csvToJsonStream
|
|
387
|
+
*/
|
|
388
|
+
export async function csvToJsonStreamAsync(input: any, options: CsvToJsonOptions = {}): Promise<ReadableStream<any>> {
|
|
389
|
+
return csvToJsonStream(input, options);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Экспорт для Node.js совместимости
|
|
393
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
394
|
+
module.exports = {
|
|
395
|
+
jsonToCsvStream,
|
|
396
|
+
jsonToCsvStreamAsync,
|
|
397
|
+
jsonToNdjsonStream,
|
|
398
|
+
jsonToNdjsonStreamAsync,
|
|
399
|
+
csvToJsonStream,
|
|
400
|
+
csvToJsonStreamAsync,
|
|
401
|
+
createReadableStreamFromIterator
|
|
402
|
+
};
|
|
403
|
+
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// Работает в отдельном потоке, не блокируя основной
|
|
3
3
|
|
|
4
4
|
// Импорт функций парсинга (они будут bundled вместе с worker)
|
|
5
|
-
import { csvToJson } from '../csv-to-json-browser
|
|
6
|
-
import { jsonToCsv } from '../json-to-csv-browser
|
|
5
|
+
import { csvToJson } from '../csv-to-json-browser';
|
|
6
|
+
import { jsonToCsv } from '../json-to-csv-browser';
|
|
7
7
|
|
|
8
8
|
const textDecoder = new TextDecoder('utf-8');
|
|
9
9
|
|
|
@@ -374,4 +374,4 @@ if (typeof module !== 'undefined' && module.exports) {
|
|
|
374
374
|
getStats,
|
|
375
375
|
clearCache
|
|
376
376
|
};
|
|
377
|
-
}
|
|
377
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Worker Pool для параллельной обработки CSV
|
|
2
2
|
// Использует Comlink для простой коммуникации с Web Workers
|
|
3
3
|
|
|
4
|
-
import { ValidationError, ConfigurationError } from '../errors-browser
|
|
4
|
+
import { ValidationError, ConfigurationError } from '../errors-browser';
|
|
5
5
|
|
|
6
6
|
// Проверка поддержки Web Workers
|
|
7
7
|
const WORKERS_SUPPORTED = typeof Worker !== 'undefined';
|
|
@@ -37,8 +37,15 @@ function collectTransferables(args) {
|
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
args.forEach(collectFromValue);
|
|
40
|
-
return transferables.length ? transferables : null;
|
|
41
|
-
}
|
|
40
|
+
return transferables.length ? transferables : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type WorkerWithMeta = Worker & {
|
|
44
|
+
id: string;
|
|
45
|
+
status: 'idle' | 'busy' | 'error';
|
|
46
|
+
lastUsed: number;
|
|
47
|
+
taskId: string | null;
|
|
48
|
+
};
|
|
42
49
|
|
|
43
50
|
/**
|
|
44
51
|
* Опции для Worker Pool
|
|
@@ -72,8 +79,14 @@ function collectTransferables(args) {
|
|
|
72
79
|
/**
|
|
73
80
|
* Worker Pool для параллельной обработки CSV
|
|
74
81
|
*/
|
|
75
|
-
export class WorkerPool {
|
|
76
|
-
|
|
82
|
+
export class WorkerPool {
|
|
83
|
+
workerScript: string;
|
|
84
|
+
options: any;
|
|
85
|
+
workers: WorkerWithMeta[];
|
|
86
|
+
taskQueue: any[];
|
|
87
|
+
activeTasks: Map<any, any>;
|
|
88
|
+
stats: any;
|
|
89
|
+
/**
|
|
77
90
|
* Создает новый Worker Pool
|
|
78
91
|
* @param {string} workerScript - URL скрипта worker
|
|
79
92
|
* @param {WorkerPoolOptions} [options] - Опции pool
|
|
@@ -127,7 +140,7 @@ export class WorkerPool {
|
|
|
127
140
|
*/
|
|
128
141
|
createWorker() {
|
|
129
142
|
try {
|
|
130
|
-
const worker = new Worker(this.workerScript, { type: 'module' });
|
|
143
|
+
const worker = new Worker(this.workerScript, { type: 'module' }) as WorkerWithMeta;
|
|
131
144
|
|
|
132
145
|
worker.id = `worker-${this.workers.length}`;
|
|
133
146
|
worker.status = 'idle';
|
|
@@ -229,11 +242,11 @@ export class WorkerPool {
|
|
|
229
242
|
|
|
230
243
|
// Завершение с ошибкой
|
|
231
244
|
const workerError = new Error(errorData.message || 'Ошибка в worker');
|
|
232
|
-
if (errorData.code) {
|
|
233
|
-
workerError.code = errorData.code;
|
|
245
|
+
if ((errorData as any).code) {
|
|
246
|
+
(workerError as any).code = (errorData as any).code;
|
|
234
247
|
}
|
|
235
|
-
if (errorData.details) {
|
|
236
|
-
workerError.details = errorData.details;
|
|
248
|
+
if ((errorData as any).details) {
|
|
249
|
+
(workerError as any).details = (errorData as any).details;
|
|
237
250
|
}
|
|
238
251
|
task.reject(workerError);
|
|
239
252
|
this.activeTasks.delete(taskId);
|
|
@@ -363,9 +376,9 @@ export class WorkerPool {
|
|
|
363
376
|
* @param {Array} args - Аргументы метода
|
|
364
377
|
* @param {Object} [options] - Опции задачи
|
|
365
378
|
* @param {Function} [onProgress] - Callback прогресса
|
|
366
|
-
* @returns {Promise<
|
|
379
|
+
* @returns {Promise<unknown>} Результат выполнения
|
|
367
380
|
*/
|
|
368
|
-
async exec(method, args = [], options = {}, onProgress = null) {
|
|
381
|
+
async exec(method, args = [], options: any = {}, onProgress = null) {
|
|
369
382
|
return new Promise((resolve, reject) => {
|
|
370
383
|
// Проверка размера очереди
|
|
371
384
|
if (this.taskQueue.length >= this.options.maxQueueSize) {
|
|
@@ -457,10 +470,16 @@ export class WorkerPool {
|
|
|
457
470
|
* @param {WorkerPoolOptions} [options] - Опции pool
|
|
458
471
|
* @returns {WorkerPool} Worker Pool
|
|
459
472
|
*/
|
|
460
|
-
export function createWorkerPool(options = {}) {
|
|
473
|
+
export function createWorkerPool(options: any = {}): any {
|
|
461
474
|
// Используем встроенный worker скрипт
|
|
462
|
-
const
|
|
463
|
-
|
|
475
|
+
const baseUrl =
|
|
476
|
+
typeof document !== 'undefined'
|
|
477
|
+
? document.baseURI
|
|
478
|
+
: (typeof self !== 'undefined' && (self as any).location
|
|
479
|
+
? (self as any).location.href
|
|
480
|
+
: '');
|
|
481
|
+
const workerScript = new URL('./csv-parser.worker.js', baseUrl).href;
|
|
482
|
+
return new WorkerPool(workerScript, options);
|
|
464
483
|
}
|
|
465
484
|
|
|
466
485
|
/**
|
|
@@ -470,13 +489,14 @@ export function createWorkerPool(options = {}) {
|
|
|
470
489
|
* @param {Function} [onProgress] - Callback прогресса
|
|
471
490
|
* @returns {Promise<Array<Object>>} JSON данные
|
|
472
491
|
*/
|
|
473
|
-
export async function parseCSVWithWorker(csvInput, options = {}, onProgress = null) {
|
|
492
|
+
export async function parseCSVWithWorker(csvInput, options: any = {}, onProgress = null) {
|
|
474
493
|
// Создание pool если нужно
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
494
|
+
const poolHolder = parseCSVWithWorker as any;
|
|
495
|
+
if (!poolHolder.pool) {
|
|
496
|
+
poolHolder.pool = createWorkerPool();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const pool = poolHolder.pool;
|
|
480
500
|
|
|
481
501
|
// Подготовка CSV строки
|
|
482
502
|
// ?????????? CSV ??????
|
|
@@ -508,20 +528,21 @@ export async function parseCSVWithWorker(csvInput, options = {}, onProgress = nu
|
|
|
508
528
|
* Чтение файла как текст
|
|
509
529
|
* @private
|
|
510
530
|
*/
|
|
511
|
-
async function readFileAsArrayBuffer(file) {
|
|
531
|
+
async function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> {
|
|
512
532
|
return new Promise((resolve, reject) => {
|
|
513
533
|
const reader = new FileReader();
|
|
514
|
-
reader.onload = (event) =>
|
|
534
|
+
reader.onload = (event) =>
|
|
535
|
+
resolve((event.target as FileReader).result as ArrayBuffer);
|
|
515
536
|
reader.onerror = (error) => reject(error);
|
|
516
537
|
reader.readAsArrayBuffer(file);
|
|
517
538
|
});
|
|
518
539
|
}
|
|
519
540
|
|
|
520
541
|
// Экспорт для Node.js совместимости
|
|
521
|
-
if (typeof module !== 'undefined' && module.exports) {
|
|
522
|
-
module.exports = {
|
|
523
|
-
WorkerPool,
|
|
524
|
-
createWorkerPool,
|
|
525
|
-
parseCSVWithWorker
|
|
526
|
-
};
|
|
527
|
-
}
|
|
542
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
543
|
+
module.exports = {
|
|
544
|
+
WorkerPool,
|
|
545
|
+
createWorkerPool,
|
|
546
|
+
parseCSVWithWorker
|
|
547
|
+
};
|
|
548
|
+
}
|