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
package/src/browser/streams.js
DELETED
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ValidationError,
|
|
3
|
-
ConfigurationError,
|
|
4
|
-
LimitError
|
|
5
|
-
} from './errors-browser.js';
|
|
6
|
-
import { csvToJsonIterator } from './csv-to-json-browser.js';
|
|
7
|
-
|
|
8
|
-
const DEFAULT_MAX_CHUNK_SIZE = 64 * 1024;
|
|
9
|
-
|
|
10
|
-
function isReadableStream(value) {
|
|
11
|
-
return value && typeof value.getReader === 'function';
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function isAsyncIterable(value) {
|
|
15
|
-
return value && typeof value[Symbol.asyncIterator] === 'function';
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function isIterable(value) {
|
|
19
|
-
return value && typeof value[Symbol.iterator] === 'function';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function createReadableStreamFromIterator(iterator) {
|
|
23
|
-
return new ReadableStream({
|
|
24
|
-
async pull(controller) {
|
|
25
|
-
try {
|
|
26
|
-
const { value, done } = await iterator.next();
|
|
27
|
-
if (done) {
|
|
28
|
-
controller.close();
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
controller.enqueue(value);
|
|
32
|
-
} catch (error) {
|
|
33
|
-
controller.error(error);
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
cancel() {
|
|
37
|
-
if (iterator.return) {
|
|
38
|
-
iterator.return();
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function detectInputFormat(input, options) {
|
|
45
|
-
if (options && options.inputFormat) {
|
|
46
|
-
return options.inputFormat;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (typeof input === 'string') {
|
|
50
|
-
const trimmed = input.trim();
|
|
51
|
-
if (trimmed.startsWith('[')) {
|
|
52
|
-
return 'json-array';
|
|
53
|
-
}
|
|
54
|
-
if (trimmed.includes('\n')) {
|
|
55
|
-
return 'ndjson';
|
|
56
|
-
}
|
|
57
|
-
return 'json-array';
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (input instanceof Blob || isReadableStream(input)) {
|
|
61
|
-
return 'ndjson';
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return 'json-array';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function* parseNdjsonText(text) {
|
|
68
|
-
const lines = text.split(/\r?\n/);
|
|
69
|
-
for (const line of lines) {
|
|
70
|
-
const trimmed = line.trim();
|
|
71
|
-
if (!trimmed) {
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
yield JSON.parse(trimmed);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function* parseNdjsonStream(stream) {
|
|
79
|
-
const reader = stream.getReader();
|
|
80
|
-
const decoder = new TextDecoder('utf-8');
|
|
81
|
-
let buffer = '';
|
|
82
|
-
|
|
83
|
-
while (true) {
|
|
84
|
-
const { value, done } = await reader.read();
|
|
85
|
-
if (done) {
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
buffer += decoder.decode(value, { stream: true });
|
|
90
|
-
const lines = buffer.split(/\r?\n/);
|
|
91
|
-
buffer = lines.pop() || '';
|
|
92
|
-
|
|
93
|
-
for (const line of lines) {
|
|
94
|
-
const trimmed = line.trim();
|
|
95
|
-
if (!trimmed) {
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
yield JSON.parse(trimmed);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (buffer.trim()) {
|
|
103
|
-
yield JSON.parse(buffer.trim());
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function* normalizeJsonInput(input, options = {}) {
|
|
108
|
-
const format = detectInputFormat(input, options);
|
|
109
|
-
|
|
110
|
-
if (Array.isArray(input)) {
|
|
111
|
-
for (const item of input) {
|
|
112
|
-
yield item;
|
|
113
|
-
}
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (isAsyncIterable(input)) {
|
|
118
|
-
for await (const item of input) {
|
|
119
|
-
yield item;
|
|
120
|
-
}
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (isIterable(input)) {
|
|
125
|
-
for (const item of input) {
|
|
126
|
-
yield item;
|
|
127
|
-
}
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (typeof input === 'string') {
|
|
132
|
-
if (format === 'ndjson') {
|
|
133
|
-
yield* parseNdjsonText(input);
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const parsed = JSON.parse(input);
|
|
138
|
-
if (Array.isArray(parsed)) {
|
|
139
|
-
for (const item of parsed) {
|
|
140
|
-
yield item;
|
|
141
|
-
}
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
yield parsed;
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (input instanceof Blob) {
|
|
149
|
-
if (format === 'ndjson') {
|
|
150
|
-
yield* parseNdjsonStream(input.stream());
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const text = await input.text();
|
|
155
|
-
const parsed = JSON.parse(text);
|
|
156
|
-
if (Array.isArray(parsed)) {
|
|
157
|
-
for (const item of parsed) {
|
|
158
|
-
yield item;
|
|
159
|
-
}
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
yield parsed;
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (isReadableStream(input)) {
|
|
167
|
-
if (format !== 'ndjson') {
|
|
168
|
-
throw new ValidationError('ReadableStream input requires inputFormat="ndjson"');
|
|
169
|
-
}
|
|
170
|
-
yield* parseNdjsonStream(input);
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
throw new ValidationError('Input must be an array, iterable, string, Blob, or ReadableStream');
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function validateStreamOptions(options) {
|
|
178
|
-
if (options && typeof options !== 'object') {
|
|
179
|
-
throw new ConfigurationError('Options must be an object');
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (options?.delimiter && typeof options.delimiter !== 'string') {
|
|
183
|
-
throw new ConfigurationError('Delimiter must be a string');
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (options?.delimiter && options.delimiter.length !== 1) {
|
|
187
|
-
throw new ConfigurationError('Delimiter must be a single character');
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (options?.renameMap && typeof options.renameMap !== 'object') {
|
|
191
|
-
throw new ConfigurationError('renameMap must be an object');
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (options?.maxRecords !== undefined) {
|
|
195
|
-
if (typeof options.maxRecords !== 'number' || options.maxRecords <= 0) {
|
|
196
|
-
throw new ConfigurationError('maxRecords must be a positive number');
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function escapeCsvValue(value, options) {
|
|
202
|
-
const {
|
|
203
|
-
delimiter,
|
|
204
|
-
preventCsvInjection = true,
|
|
205
|
-
rfc4180Compliant = true
|
|
206
|
-
} = options;
|
|
207
|
-
|
|
208
|
-
if (value === null || value === undefined || value === '') {
|
|
209
|
-
return '';
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const stringValue = String(value);
|
|
213
|
-
let escapedValue = stringValue;
|
|
214
|
-
if (preventCsvInjection && /^[=+\-@]/.test(stringValue)) {
|
|
215
|
-
escapedValue = "'" + stringValue;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const needsQuoting = rfc4180Compliant
|
|
219
|
-
? (escapedValue.includes(delimiter) ||
|
|
220
|
-
escapedValue.includes('"') ||
|
|
221
|
-
escapedValue.includes('\n') ||
|
|
222
|
-
escapedValue.includes('\r'))
|
|
223
|
-
: (escapedValue.includes(delimiter) ||
|
|
224
|
-
escapedValue.includes('"') ||
|
|
225
|
-
escapedValue.includes('\n') ||
|
|
226
|
-
escapedValue.includes('\r'));
|
|
227
|
-
|
|
228
|
-
if (needsQuoting) {
|
|
229
|
-
return `"${escapedValue.replace(/"/g, '""')}"`;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return escapedValue;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function buildHeaderState(keys, options) {
|
|
236
|
-
const renameMap = options.renameMap || {};
|
|
237
|
-
const template = options.template || {};
|
|
238
|
-
const originalKeys = Array.isArray(options.headers) ? options.headers : keys;
|
|
239
|
-
const headers = originalKeys.map((key) => renameMap[key] || key);
|
|
240
|
-
|
|
241
|
-
const reverseRenameMap = {};
|
|
242
|
-
originalKeys.forEach((key, index) => {
|
|
243
|
-
reverseRenameMap[headers[index]] = key;
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
let finalHeaders = headers;
|
|
247
|
-
if (Object.keys(template).length > 0) {
|
|
248
|
-
const templateHeaders = Object.keys(template).map(key => renameMap[key] || key);
|
|
249
|
-
const extraHeaders = headers.filter(h => !templateHeaders.includes(h));
|
|
250
|
-
finalHeaders = [...templateHeaders, ...extraHeaders];
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return {
|
|
254
|
-
headers: finalHeaders,
|
|
255
|
-
reverseRenameMap
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async function* jsonToCsvChunkIterator(input, options = {}) {
|
|
260
|
-
validateStreamOptions(options);
|
|
261
|
-
|
|
262
|
-
const opts = options && typeof options === 'object' ? options : {};
|
|
263
|
-
const {
|
|
264
|
-
delimiter = ';',
|
|
265
|
-
includeHeaders = true,
|
|
266
|
-
maxRecords,
|
|
267
|
-
maxChunkSize = DEFAULT_MAX_CHUNK_SIZE,
|
|
268
|
-
headerMode
|
|
269
|
-
} = opts;
|
|
270
|
-
|
|
271
|
-
let headerState = null;
|
|
272
|
-
let buffer = '';
|
|
273
|
-
let recordCount = 0;
|
|
274
|
-
const lineEnding = opts.rfc4180Compliant === false ? '\n' : '\r\n';
|
|
275
|
-
|
|
276
|
-
if (Array.isArray(input) && !opts.headers && (!headerMode || headerMode === 'all')) {
|
|
277
|
-
const allKeys = new Set();
|
|
278
|
-
for (const item of input) {
|
|
279
|
-
if (!item || typeof item !== 'object') {
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
Object.keys(item).forEach((key) => allKeys.add(key));
|
|
283
|
-
}
|
|
284
|
-
headerState = buildHeaderState(Array.from(allKeys), opts);
|
|
285
|
-
if (includeHeaders && headerState.headers.length > 0) {
|
|
286
|
-
buffer += headerState.headers.join(delimiter) + lineEnding;
|
|
287
|
-
}
|
|
288
|
-
} else if (Array.isArray(opts.headers)) {
|
|
289
|
-
headerState = buildHeaderState(opts.headers, opts);
|
|
290
|
-
if (includeHeaders && headerState.headers.length > 0) {
|
|
291
|
-
buffer += headerState.headers.join(delimiter) + lineEnding;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
for await (const item of normalizeJsonInput(input, opts)) {
|
|
296
|
-
if (!item || typeof item !== 'object') {
|
|
297
|
-
continue;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (!headerState) {
|
|
301
|
-
headerState = buildHeaderState(Object.keys(item), opts);
|
|
302
|
-
if (includeHeaders && headerState.headers.length > 0) {
|
|
303
|
-
buffer += headerState.headers.join(delimiter) + lineEnding;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
recordCount += 1;
|
|
308
|
-
if (maxRecords && recordCount > maxRecords) {
|
|
309
|
-
throw new LimitError(
|
|
310
|
-
`Data size exceeds maximum limit of ${maxRecords} records`,
|
|
311
|
-
maxRecords,
|
|
312
|
-
recordCount
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const row = headerState.headers.map((header) => {
|
|
317
|
-
const originalKey = headerState.reverseRenameMap[header] || header;
|
|
318
|
-
return escapeCsvValue(item[originalKey], {
|
|
319
|
-
delimiter,
|
|
320
|
-
preventCsvInjection: opts.preventCsvInjection !== false,
|
|
321
|
-
rfc4180Compliant: opts.rfc4180Compliant !== false
|
|
322
|
-
});
|
|
323
|
-
}).join(delimiter);
|
|
324
|
-
|
|
325
|
-
buffer += row + lineEnding;
|
|
326
|
-
|
|
327
|
-
if (buffer.length >= maxChunkSize) {
|
|
328
|
-
yield buffer;
|
|
329
|
-
buffer = '';
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (buffer.length > 0) {
|
|
334
|
-
yield buffer;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
async function* jsonToNdjsonChunkIterator(input, options = {}) {
|
|
339
|
-
validateStreamOptions(options);
|
|
340
|
-
for await (const item of normalizeJsonInput(input, options)) {
|
|
341
|
-
if (item === undefined) {
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
yield JSON.stringify(item) + '\n';
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
async function* csvToJsonChunkIterator(input, options = {}) {
|
|
349
|
-
const outputFormat = options.outputFormat || 'ndjson';
|
|
350
|
-
const asArray = outputFormat === 'json-array' || outputFormat === 'array' || outputFormat === 'json';
|
|
351
|
-
let first = true;
|
|
352
|
-
|
|
353
|
-
if (asArray) {
|
|
354
|
-
yield '[';
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
for await (const row of csvToJsonIterator(input, options)) {
|
|
358
|
-
const payload = JSON.stringify(row);
|
|
359
|
-
if (asArray) {
|
|
360
|
-
yield (first ? '' : ',') + payload;
|
|
361
|
-
} else {
|
|
362
|
-
yield payload + '\n';
|
|
363
|
-
}
|
|
364
|
-
first = false;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
if (asArray) {
|
|
368
|
-
yield ']';
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
export function jsonToCsvStream(input, options = {}) {
|
|
373
|
-
const iterator = jsonToCsvChunkIterator(input, options);
|
|
374
|
-
return createReadableStreamFromIterator(iterator);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
export function jsonToNdjsonStream(input, options = {}) {
|
|
378
|
-
const iterator = jsonToNdjsonChunkIterator(input, options);
|
|
379
|
-
return createReadableStreamFromIterator(iterator);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
export function csvToJsonStream(input, options = {}) {
|
|
383
|
-
const iterator = csvToJsonChunkIterator(input, options);
|
|
384
|
-
return createReadableStreamFromIterator(iterator);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (typeof module !== 'undefined' && module.exports) {
|
|
388
|
-
module.exports = {
|
|
389
|
-
jsonToCsvStream,
|
|
390
|
-
jsonToNdjsonStream,
|
|
391
|
-
csvToJsonStream
|
|
392
|
-
};
|
|
393
|
-
}
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Кэширование результатов авто-детектирования разделителя
|
|
3
|
-
* Использует WeakMap и LRU кэш для оптимизации производительности
|
|
4
|
-
*
|
|
5
|
-
* @version 1.0.0
|
|
6
|
-
* @date 2026-01-23
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
class DelimiterCache {
|
|
10
|
-
constructor(maxSize = 100) {
|
|
11
|
-
this.weakMap = new WeakMap();
|
|
12
|
-
this.lruCache = new Map();
|
|
13
|
-
this.maxSize = maxSize;
|
|
14
|
-
this.stats = {
|
|
15
|
-
hits: 0,
|
|
16
|
-
misses: 0,
|
|
17
|
-
evictions: 0,
|
|
18
|
-
size: 0
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Генерирует ключ кэша на основе строки и кандидатов
|
|
24
|
-
* @private
|
|
25
|
-
*/
|
|
26
|
-
_generateKey(csv, candidates) {
|
|
27
|
-
// Используем хэш первых 1000 символов для производительности
|
|
28
|
-
const sample = csv.substring(0, Math.min(1000, csv.length));
|
|
29
|
-
const candidatesKey = candidates.join(',');
|
|
30
|
-
return `${this._hashString(sample)}:${candidatesKey}`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Простая хэш-функция для строк
|
|
35
|
-
* @private
|
|
36
|
-
*/
|
|
37
|
-
_hashString(str) {
|
|
38
|
-
let hash = 0;
|
|
39
|
-
for (let i = 0; i < str.length; i++) {
|
|
40
|
-
const char = str.charCodeAt(i);
|
|
41
|
-
hash = ((hash << 5) - hash) + char;
|
|
42
|
-
hash = hash & hash; // Convert to 32bit integer
|
|
43
|
-
}
|
|
44
|
-
return hash.toString(36);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Получает значение из кэша
|
|
49
|
-
* @param {string} csv - CSV строка
|
|
50
|
-
* @param {Array} candidates - Кандидаты разделителей
|
|
51
|
-
* @returns {string|null} Кэшированный разделитель или null
|
|
52
|
-
*/
|
|
53
|
-
get(csv, candidates) {
|
|
54
|
-
const key = this._generateKey(csv, candidates);
|
|
55
|
-
|
|
56
|
-
// Проверяем LRU кэш
|
|
57
|
-
if (this.lruCache.has(key)) {
|
|
58
|
-
// Обновляем позицию в LRU
|
|
59
|
-
const value = this.lruCache.get(key);
|
|
60
|
-
this.lruCache.delete(key);
|
|
61
|
-
this.lruCache.set(key, value);
|
|
62
|
-
this.stats.hits++;
|
|
63
|
-
return value;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
this.stats.misses++;
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Сохраняет значение в кэш
|
|
72
|
-
* @param {string} csv - CSV строка
|
|
73
|
-
* @param {Array} candidates - Кандидаты разделителей
|
|
74
|
-
* @param {string} delimiter - Найденный разделитель
|
|
75
|
-
*/
|
|
76
|
-
set(csv, candidates, delimiter) {
|
|
77
|
-
const key = this._generateKey(csv, candidates);
|
|
78
|
-
|
|
79
|
-
// Проверяем необходимость вытеснения из LRU кэша
|
|
80
|
-
if (this.lruCache.size >= this.maxSize) {
|
|
81
|
-
// Удаляем самый старый элемент (первый в Map)
|
|
82
|
-
const firstKey = this.lruCache.keys().next().value;
|
|
83
|
-
this.lruCache.delete(firstKey);
|
|
84
|
-
this.stats.evictions++;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Сохраняем в LRU кэш
|
|
88
|
-
this.lruCache.set(key, delimiter);
|
|
89
|
-
this.stats.size = this.lruCache.size;
|
|
90
|
-
|
|
91
|
-
// Также сохраняем в WeakMap если csv является объектом
|
|
92
|
-
if (typeof csv === 'object' && csv !== null) {
|
|
93
|
-
this.weakMap.set(csv, { candidates, delimiter });
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Очищает кэш
|
|
99
|
-
*/
|
|
100
|
-
clear() {
|
|
101
|
-
this.lruCache.clear();
|
|
102
|
-
this.weakMap = new WeakMap();
|
|
103
|
-
this.stats = {
|
|
104
|
-
hits: 0,
|
|
105
|
-
misses: 0,
|
|
106
|
-
evictions: 0,
|
|
107
|
-
size: 0
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Возвращает статистику кэша
|
|
113
|
-
* @returns {Object} Статистика
|
|
114
|
-
*/
|
|
115
|
-
getStats() {
|
|
116
|
-
const total = this.stats.hits + this.stats.misses;
|
|
117
|
-
return {
|
|
118
|
-
...this.stats,
|
|
119
|
-
hitRate: total > 0 ? (this.stats.hits / total) * 100 : 0,
|
|
120
|
-
totalRequests: total
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Оптимизированная версия autoDetectDelimiter с кэшированием
|
|
126
|
-
* @param {string} csv - CSV строка
|
|
127
|
-
* @param {Array} candidates - Кандидаты разделителей
|
|
128
|
-
* @param {DelimiterCache} cache - Экземпляр кэша (опционально)
|
|
129
|
-
* @returns {string} Найденный разделитель
|
|
130
|
-
*/
|
|
131
|
-
static autoDetectDelimiter(csv, candidates = [';', ',', '\t', '|'], cache = null) {
|
|
132
|
-
if (!csv || typeof csv !== 'string') {
|
|
133
|
-
return ';';
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Проверяем кэш если он предоставлен
|
|
137
|
-
if (cache) {
|
|
138
|
-
const cached = cache.get(csv, candidates);
|
|
139
|
-
if (cached !== null) {
|
|
140
|
-
return cached;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const lines = csv.split('\n').filter(line => line.trim().length > 0);
|
|
145
|
-
|
|
146
|
-
if (lines.length === 0) {
|
|
147
|
-
return ';';
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Используем первую непустую строку для детектирования
|
|
151
|
-
const firstLine = lines[0];
|
|
152
|
-
|
|
153
|
-
const counts = {};
|
|
154
|
-
candidates.forEach(delim => {
|
|
155
|
-
const escapedDelim = delim.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
156
|
-
const regex = new RegExp(escapedDelim, 'g');
|
|
157
|
-
const matches = firstLine.match(regex);
|
|
158
|
-
counts[delim] = matches ? matches.length : 0;
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// Находим разделитель с максимальным количеством
|
|
162
|
-
let maxCount = -1;
|
|
163
|
-
let detectedDelimiter = ';';
|
|
164
|
-
|
|
165
|
-
for (const [delim, count] of Object.entries(counts)) {
|
|
166
|
-
if (count > maxCount) {
|
|
167
|
-
maxCount = count;
|
|
168
|
-
detectedDelimiter = delim;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Если разделитель не найден или есть ничья, возвращаем стандартный
|
|
173
|
-
if (maxCount === 0) {
|
|
174
|
-
detectedDelimiter = ';';
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Сохраняем в кэш если он предоставлен
|
|
178
|
-
if (cache) {
|
|
179
|
-
cache.set(csv, candidates, detectedDelimiter);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return detectedDelimiter;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
module.exports = DelimiterCache;
|