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/json-to-csv.js
DELETED
|
@@ -1,526 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JSON to CSV Converter - Node.js Module
|
|
3
|
-
*
|
|
4
|
-
* A lightweight, efficient module for converting JSON data to CSV format
|
|
5
|
-
* with proper escaping and formatting for Excel compatibility.
|
|
6
|
-
*
|
|
7
|
-
* @module json-to-csv
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const {
|
|
11
|
-
ValidationError,
|
|
12
|
-
SecurityError,
|
|
13
|
-
FileSystemError,
|
|
14
|
-
LimitError,
|
|
15
|
-
ConfigurationError,
|
|
16
|
-
safeExecute
|
|
17
|
-
} = require('./errors');
|
|
18
|
-
|
|
19
|
-
// Add schema validator import
|
|
20
|
-
const { createSchemaValidators } = require('./src/utils/schema-validator');
|
|
21
|
-
/**
|
|
22
|
-
* Validates input data and options
|
|
23
|
-
* @private
|
|
24
|
-
*/
|
|
25
|
-
function validateInput(data, options) {
|
|
26
|
-
// Validate data
|
|
27
|
-
if (!Array.isArray(data)) {
|
|
28
|
-
throw new ValidationError('Input data must be an array');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Validate options
|
|
32
|
-
if (options && typeof options !== 'object') {
|
|
33
|
-
throw new ConfigurationError('Options must be an object');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Validate delimiter
|
|
37
|
-
if (options?.delimiter && typeof options.delimiter !== 'string') {
|
|
38
|
-
throw new ConfigurationError('Delimiter must be a string');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (options?.delimiter && options.delimiter.length !== 1) {
|
|
42
|
-
throw new ConfigurationError('Delimiter must be a single character');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Validate renameMap
|
|
46
|
-
if (options?.renameMap && typeof options.renameMap !== 'object') {
|
|
47
|
-
throw new ConfigurationError('renameMap must be an object');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Validate maxRecords
|
|
51
|
-
if (options && options.maxRecords !== undefined) {
|
|
52
|
-
if (typeof options.maxRecords !== 'number' || options.maxRecords <= 0) {
|
|
53
|
-
throw new ConfigurationError('maxRecords must be a positive number');
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Validate preventCsvInjection
|
|
58
|
-
if (options?.preventCsvInjection !== undefined && typeof options.preventCsvInjection !== 'boolean') {
|
|
59
|
-
throw new ConfigurationError('preventCsvInjection must be a boolean');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Validate rfc4180Compliant
|
|
63
|
-
if (options?.rfc4180Compliant !== undefined && typeof options.rfc4180Compliant !== 'boolean') {
|
|
64
|
-
throw new ConfigurationError('rfc4180Compliant must be a boolean');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Validate schema
|
|
68
|
-
if (options?.schema && typeof options.schema !== 'object') {
|
|
69
|
-
throw new ConfigurationError('schema must be an object');
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Converts JSON data to CSV format
|
|
77
|
-
*
|
|
78
|
-
* @param {Array<Object>} data - Array of objects to convert to CSV
|
|
79
|
-
* @param {Object} [options] - Configuration options
|
|
80
|
-
* @param {string} [options.delimiter=';'] - CSV delimiter character
|
|
81
|
-
* @param {boolean} [options.includeHeaders=true] - Whether to include headers row
|
|
82
|
-
* @param {Object} [options.renameMap={}] - Map for renaming column headers (oldKey: newKey)
|
|
83
|
-
* @param {Object} [options.template={}] - Template object to ensure consistent column order
|
|
84
|
-
* @param {number} [options.maxRecords] - Maximum number of records to process (optional, no limit by default)
|
|
85
|
-
* @param {boolean} [options.preventCsvInjection=true] - Prevent CSV injection attacks by escaping formulas
|
|
86
|
-
* @param {boolean} [options.rfc4180Compliant=true] - Ensure RFC 4180 compliance (proper quoting, line endings)
|
|
87
|
-
* @param {Object} [options.schema] - JSON schema for data validation and formatting
|
|
88
|
-
* @returns {string} CSV formatted string
|
|
89
|
-
*
|
|
90
|
-
* @example
|
|
91
|
-
* const jsonToCsv = require('./json-to-csv');
|
|
92
|
-
*
|
|
93
|
-
* const data = [
|
|
94
|
-
* { id: 1, name: 'John', email: 'john@example.com' },
|
|
95
|
-
* { id: 2, name: 'Jane', email: 'jane@example.com' }
|
|
96
|
-
* ];
|
|
97
|
-
*
|
|
98
|
-
* const csv = jsonToCsv(data, {
|
|
99
|
-
* delimiter: ',',
|
|
100
|
-
* renameMap: { id: 'ID', name: 'Full Name' },
|
|
101
|
-
* preventCsvInjection: true,
|
|
102
|
-
* rfc4180Compliant: true
|
|
103
|
-
* });
|
|
104
|
-
*/
|
|
105
|
-
function jsonToCsv(data, options = {}) {
|
|
106
|
-
return safeExecute(() => {
|
|
107
|
-
// Validate input
|
|
108
|
-
validateInput(data, options);
|
|
109
|
-
|
|
110
|
-
const opts = options && typeof options === 'object' ? options : {};
|
|
111
|
-
|
|
112
|
-
const {
|
|
113
|
-
delimiter = ';',
|
|
114
|
-
includeHeaders = true,
|
|
115
|
-
renameMap = {},
|
|
116
|
-
template = {},
|
|
117
|
-
maxRecords,
|
|
118
|
-
preventCsvInjection = true,
|
|
119
|
-
rfc4180Compliant = true,
|
|
120
|
-
schema = null
|
|
121
|
-
} = opts;
|
|
122
|
-
|
|
123
|
-
// Initialize schema validators if schema is provided
|
|
124
|
-
let schemaValidators = null;
|
|
125
|
-
if (schema) {
|
|
126
|
-
schemaValidators = createSchemaValidators(schema);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Handle empty data
|
|
130
|
-
if (data.length === 0) {
|
|
131
|
-
return '';
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Show warning for large datasets (optional limit)
|
|
135
|
-
if (data.length > 1000000 && !maxRecords && process.env.NODE_ENV !== 'test') {
|
|
136
|
-
console.warn(
|
|
137
|
-
'⚠️ Warning: Processing >1M records in memory may be slow.\n' +
|
|
138
|
-
'💡 Consider processing data in batches or using streaming for large files.\n' +
|
|
139
|
-
'📊 Current size: ' + data.length.toLocaleString() + ' records\n' +
|
|
140
|
-
'🔧 Tip: Use { maxRecords: N } option to set a custom limit if needed.'
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Apply optional record limit if specified
|
|
145
|
-
if (maxRecords && data.length > maxRecords) {
|
|
146
|
-
throw new LimitError(
|
|
147
|
-
`Data size exceeds maximum limit of ${maxRecords} records`,
|
|
148
|
-
maxRecords,
|
|
149
|
-
data.length
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Get all unique keys from all objects with minimal allocations.
|
|
154
|
-
const allKeys = new Set();
|
|
155
|
-
const originalKeys = [];
|
|
156
|
-
for (let i = 0; i < data.length; i++) {
|
|
157
|
-
const item = data[i];
|
|
158
|
-
if (!item || typeof item !== 'object') {
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
for (const key in item) {
|
|
162
|
-
if (Object.prototype.hasOwnProperty.call(item, key) && !allKeys.has(key)) {
|
|
163
|
-
allKeys.add(key);
|
|
164
|
-
originalKeys.push(key);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const hasRenameMap = Object.keys(renameMap).length > 0;
|
|
170
|
-
const hasTemplate = Object.keys(template).length > 0;
|
|
171
|
-
|
|
172
|
-
// Apply rename map to create header names.
|
|
173
|
-
let headers = originalKeys;
|
|
174
|
-
let reverseRenameMap = null;
|
|
175
|
-
if (hasRenameMap) {
|
|
176
|
-
headers = new Array(originalKeys.length);
|
|
177
|
-
reverseRenameMap = {};
|
|
178
|
-
for (let i = 0; i < originalKeys.length; i++) {
|
|
179
|
-
const key = originalKeys[i];
|
|
180
|
-
const header = renameMap[key] || key;
|
|
181
|
-
headers[i] = header;
|
|
182
|
-
reverseRenameMap[header] = key;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Apply template ordering if provided.
|
|
187
|
-
let finalHeaders = headers;
|
|
188
|
-
if (hasTemplate) {
|
|
189
|
-
const templateKeys = Object.keys(template);
|
|
190
|
-
const templateHeaders = hasRenameMap
|
|
191
|
-
? templateKeys.map(key => renameMap[key] || key)
|
|
192
|
-
: templateKeys;
|
|
193
|
-
const templateHeaderSet = new Set(templateHeaders);
|
|
194
|
-
const extraHeaders = [];
|
|
195
|
-
for (let i = 0; i < headers.length; i++) {
|
|
196
|
-
const header = headers[i];
|
|
197
|
-
if (!templateHeaderSet.has(header)) {
|
|
198
|
-
extraHeaders.push(header);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
finalHeaders = templateHeaders.concat(extraHeaders);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const finalKeys = new Array(finalHeaders.length);
|
|
205
|
-
if (hasRenameMap) {
|
|
206
|
-
for (let i = 0; i < finalHeaders.length; i++) {
|
|
207
|
-
const header = finalHeaders[i];
|
|
208
|
-
finalKeys[i] = reverseRenameMap[header] || header;
|
|
209
|
-
}
|
|
210
|
-
} else {
|
|
211
|
-
for (let i = 0; i < finalHeaders.length; i++) {
|
|
212
|
-
finalKeys[i] = finalHeaders[i];
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Escapes a value for CSV format with CSV injection protection
|
|
218
|
-
*
|
|
219
|
-
* @private
|
|
220
|
-
* @param {*} value - The value to escape
|
|
221
|
-
* @returns {string} Escaped CSV value
|
|
222
|
-
*/
|
|
223
|
-
const quoteRegex = /"/g;
|
|
224
|
-
const delimiterCode = delimiter.charCodeAt(0);
|
|
225
|
-
|
|
226
|
-
const escapeValue = (value) => {
|
|
227
|
-
if (value === null || value === undefined || value === '') {
|
|
228
|
-
return '';
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
let stringValue = value;
|
|
232
|
-
if (typeof stringValue !== 'string') {
|
|
233
|
-
stringValue = String(stringValue);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// CSV Injection protection - escape formulas if enabled
|
|
237
|
-
let escapedValue = stringValue;
|
|
238
|
-
if (preventCsvInjection) {
|
|
239
|
-
const firstCharCode = stringValue.charCodeAt(0);
|
|
240
|
-
if (firstCharCode === 61 || firstCharCode === 43 || firstCharCode === 45 || firstCharCode === 64) {
|
|
241
|
-
escapedValue = "'" + stringValue;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
let needsQuoting = false;
|
|
246
|
-
let hasQuote = false;
|
|
247
|
-
for (let i = 0; i < escapedValue.length; i++) {
|
|
248
|
-
const code = escapedValue.charCodeAt(i);
|
|
249
|
-
if (code === 34) {
|
|
250
|
-
hasQuote = true;
|
|
251
|
-
needsQuoting = true;
|
|
252
|
-
} else if (code === delimiterCode || code === 10 || code === 13) {
|
|
253
|
-
needsQuoting = true;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (needsQuoting) {
|
|
258
|
-
const quotedValue = hasQuote ? escapedValue.replace(quoteRegex, '""') : escapedValue;
|
|
259
|
-
return `"${quotedValue}"`;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return escapedValue;
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
// Build CSV rows.
|
|
266
|
-
const rows = [];
|
|
267
|
-
const columnCount = finalHeaders.length;
|
|
268
|
-
|
|
269
|
-
// Add headers row if requested.
|
|
270
|
-
if (includeHeaders && columnCount > 0) {
|
|
271
|
-
rows.push(finalHeaders.join(delimiter));
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Add data rows.
|
|
275
|
-
const rowValues = new Array(columnCount);
|
|
276
|
-
for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
|
|
277
|
-
const item = data[rowIndex];
|
|
278
|
-
if (!item || typeof item !== 'object') {
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Apply schema validation and formatting if schema is provided
|
|
283
|
-
let processedItem = item;
|
|
284
|
-
if (schemaValidators) {
|
|
285
|
-
processedItem = { ...item };
|
|
286
|
-
for (const [key, validator] of Object.entries(schemaValidators)) {
|
|
287
|
-
if (key in processedItem) {
|
|
288
|
-
const value = processedItem[key];
|
|
289
|
-
// Validate value
|
|
290
|
-
if (!validator.validate(value)) {
|
|
291
|
-
throw new ValidationError(
|
|
292
|
-
`Row ${rowIndex + 1}: Value for field "${key}" does not match schema`
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
// Format value if formatter exists
|
|
296
|
-
if (validator.format) {
|
|
297
|
-
processedItem[key] = validator.format(value);
|
|
298
|
-
}
|
|
299
|
-
} else if (validator.required) {
|
|
300
|
-
throw new ValidationError(
|
|
301
|
-
`Row ${rowIndex + 1}: Required field "${key}" is missing`
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
for (let i = 0; i < columnCount; i++) {
|
|
308
|
-
rowValues[i] = escapeValue(processedItem[finalKeys[i]]);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
rows.push(rowValues.join(delimiter));
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// RFC 4180: Each record is located on a separate line, delimited by a line break (CRLF).
|
|
315
|
-
const lineEnding = rfc4180Compliant ? '\r\n' : '\n';
|
|
316
|
-
return rows.join(lineEnding);
|
|
317
|
-
}, 'PARSE_FAILED', { function: 'jsonToCsv' });
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Deeply unwraps nested objects and arrays to extract primitive values
|
|
322
|
-
*
|
|
323
|
-
* @param {*} value - Value to unwrap
|
|
324
|
-
* @param {number} [depth=0] - Current recursion depth
|
|
325
|
-
* @param {number} [maxDepth=5] - Maximum recursion depth
|
|
326
|
-
* @param {Set} [visited=new Set()] - Set of visited objects to detect circular references
|
|
327
|
-
* @returns {string} Unwrapped string value
|
|
328
|
-
*
|
|
329
|
-
* @private
|
|
330
|
-
*/
|
|
331
|
-
function deepUnwrap(value, depth = 0, maxDepth = 5, visited = new Set()) {
|
|
332
|
-
// Check depth before processing
|
|
333
|
-
if (depth >= maxDepth) {
|
|
334
|
-
return '[Too Deep]';
|
|
335
|
-
}
|
|
336
|
-
if (value === null || value === undefined) {
|
|
337
|
-
return '';
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Handle circular references - return early for circular refs
|
|
341
|
-
if (typeof value === 'object') {
|
|
342
|
-
if (visited.has(value)) {
|
|
343
|
-
return '[Circular Reference]';
|
|
344
|
-
}
|
|
345
|
-
visited.add(value);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Handle arrays - join all elements
|
|
349
|
-
if (Array.isArray(value)) {
|
|
350
|
-
if (value.length === 0) {
|
|
351
|
-
return '';
|
|
352
|
-
}
|
|
353
|
-
const unwrappedItems = value.map(item =>
|
|
354
|
-
deepUnwrap(item, depth + 1, maxDepth, visited)
|
|
355
|
-
).filter(item => item !== '');
|
|
356
|
-
return unwrappedItems.join(', ');
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Handle objects
|
|
360
|
-
if (typeof value === 'object') {
|
|
361
|
-
const keys = Object.keys(value);
|
|
362
|
-
if (keys.length === 0) {
|
|
363
|
-
return '';
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// For maxDepth = 0 or 1, return [Too Deep] for objects
|
|
367
|
-
if (depth + 1 >= maxDepth) {
|
|
368
|
-
return '[Too Deep]';
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Stringify complex objects
|
|
372
|
-
try {
|
|
373
|
-
return JSON.stringify(value);
|
|
374
|
-
} catch (error) {
|
|
375
|
-
// Check if it's a circular reference
|
|
376
|
-
if (error.message.includes('circular') || error.message.includes('Converting circular')) {
|
|
377
|
-
return '[Circular Reference]';
|
|
378
|
-
}
|
|
379
|
-
return '[Unstringifiable Object]';
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Convert to string for primitive values
|
|
384
|
-
return String(value);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Preprocesses JSON data by deeply unwrapping nested structures
|
|
389
|
-
*
|
|
390
|
-
* @param {Array<Object>} data - Array of objects to preprocess
|
|
391
|
-
* @returns {Array<Object>} Preprocessed data with unwrapped values
|
|
392
|
-
*
|
|
393
|
-
* @example
|
|
394
|
-
* const processed = preprocessData(complexJsonData);
|
|
395
|
-
* const csv = jsonToCsv(processed);
|
|
396
|
-
*/
|
|
397
|
-
function preprocessData(data) {
|
|
398
|
-
if (!Array.isArray(data)) {
|
|
399
|
-
return [];
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
return data.map(item => {
|
|
403
|
-
if (!item || typeof item !== 'object') {
|
|
404
|
-
return {};
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const processed = {};
|
|
408
|
-
|
|
409
|
-
for (const key in item) {
|
|
410
|
-
if (Object.prototype.hasOwnProperty.call(item, key)) {
|
|
411
|
-
const value = item[key];
|
|
412
|
-
if (value && typeof value === 'object') {
|
|
413
|
-
processed[key] = deepUnwrap(value);
|
|
414
|
-
} else {
|
|
415
|
-
processed[key] = value;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return processed;
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* Validates file path to prevent path traversal attacks
|
|
426
|
-
* @private
|
|
427
|
-
*/
|
|
428
|
-
function validateFilePath(filePath) {
|
|
429
|
-
const path = require('path');
|
|
430
|
-
|
|
431
|
-
// Basic validation
|
|
432
|
-
if (typeof filePath !== 'string' || filePath.trim() === '') {
|
|
433
|
-
throw new ValidationError('File path must be a non-empty string');
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Ensure file has .csv extension
|
|
437
|
-
if (!filePath.toLowerCase().endsWith('.csv')) {
|
|
438
|
-
throw new ValidationError('File must have .csv extension');
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Block UNC paths BEFORE path.resolve() to avoid network lookup timeouts
|
|
442
|
-
if (filePath.startsWith('\\\\') || filePath.startsWith('//')) {
|
|
443
|
-
throw new SecurityError('UNC paths are not allowed');
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// Get absolute path and check for traversal
|
|
447
|
-
const absolutePath = path.resolve(filePath);
|
|
448
|
-
const normalizedPath = path.normalize(filePath);
|
|
449
|
-
|
|
450
|
-
// Prevent directory traversal attacks
|
|
451
|
-
// Check if normalized path contains parent directory references
|
|
452
|
-
if (normalizedPath.includes('..') ||
|
|
453
|
-
/\\\.\.\\|\/\.\.\//.test(filePath) ||
|
|
454
|
-
filePath.startsWith('..') ||
|
|
455
|
-
filePath.includes('/..')) {
|
|
456
|
-
throw new SecurityError('Directory traversal detected in file path');
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
return absolutePath;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* Converts JSON to CSV and saves it to a file
|
|
464
|
-
*
|
|
465
|
-
* @param {Array<Object>} data - Array of objects to convert
|
|
466
|
-
* @param {string} filePath - Path to save the CSV file
|
|
467
|
-
* @param {Object} [options] - Configuration options (same as jsonToCsv)
|
|
468
|
-
* @returns {Promise<void>}
|
|
469
|
-
*
|
|
470
|
-
* @example
|
|
471
|
-
* const { saveAsCsv } = require('./json-to-csv');
|
|
472
|
-
*
|
|
473
|
-
* await saveAsCsv(data, './output.csv', {
|
|
474
|
-
* delimiter: ',',
|
|
475
|
-
* renameMap: { id: 'ID' }
|
|
476
|
-
* });
|
|
477
|
-
*/
|
|
478
|
-
async function saveAsCsv(data, filePath, options = {}) {
|
|
479
|
-
return safeExecute(async () => {
|
|
480
|
-
const fs = require('fs').promises;
|
|
481
|
-
|
|
482
|
-
// Validate file path
|
|
483
|
-
const safePath = validateFilePath(filePath);
|
|
484
|
-
|
|
485
|
-
// Convert data to CSV
|
|
486
|
-
const csvContent = jsonToCsv(data, options);
|
|
487
|
-
|
|
488
|
-
// Ensure directory exists
|
|
489
|
-
const dir = require('path').dirname(safePath);
|
|
490
|
-
|
|
491
|
-
try {
|
|
492
|
-
await fs.mkdir(dir, { recursive: true });
|
|
493
|
-
|
|
494
|
-
// Write file
|
|
495
|
-
await fs.writeFile(safePath, csvContent, 'utf8');
|
|
496
|
-
|
|
497
|
-
return safePath;
|
|
498
|
-
} catch (error) {
|
|
499
|
-
if (error.code === 'ENOENT') {
|
|
500
|
-
throw new FileSystemError(`Directory does not exist: ${dir}`, error);
|
|
501
|
-
}
|
|
502
|
-
if (error.code === 'EACCES') {
|
|
503
|
-
throw new FileSystemError(`Permission denied: ${safePath}`, error);
|
|
504
|
-
}
|
|
505
|
-
if (error.code === 'ENOSPC') {
|
|
506
|
-
throw new FileSystemError(`No space left on device: ${safePath}`, error);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
throw new FileSystemError(`Failed to write CSV file: ${error.message}`, error);
|
|
510
|
-
}
|
|
511
|
-
}, 'FILE_SYSTEM_ERROR', { function: 'saveAsCsv' });
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// Export the main functions
|
|
515
|
-
module.exports = {
|
|
516
|
-
jsonToCsv,
|
|
517
|
-
preprocessData,
|
|
518
|
-
saveAsCsv,
|
|
519
|
-
deepUnwrap,
|
|
520
|
-
validateFilePath
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
// For ES6 module compatibility
|
|
524
|
-
if (typeof module !== 'undefined' && module.exports) {
|
|
525
|
-
module.exports.default = jsonToCsv;
|
|
526
|
-
}
|
package/plugins/README.md
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# JTCSV framework integrations
|
|
2
|
-
|
|
3
|
-
This folder contains optional adapters published as separate packages. Each adapter depends on `jtcsv`.
|
|
4
|
-
|
|
5
|
-
## Available packages
|
|
6
|
-
- @jtcsv/express-middleware
|
|
7
|
-
- @jtcsv/fastify
|
|
8
|
-
- @jtcsv/nextjs
|
|
9
|
-
- @jtcsv/nestjs
|
|
10
|
-
- @jtcsv/remix
|
|
11
|
-
- @jtcsv/nuxt
|
|
12
|
-
- @jtcsv/sveltekit
|
|
13
|
-
- @jtcsv/hono
|
|
14
|
-
- @jtcsv/trpc
|
|
15
|
-
|
|
16
|
-
## Main package shortcuts
|
|
17
|
-
If you already depend on `jtcsv`, the adapters are also exported from the main package:
|
|
18
|
-
- jtcsv/express
|
|
19
|
-
- jtcsv/fastify
|
|
20
|
-
- jtcsv/nextjs
|
|
21
|
-
- jtcsv/nextjs/route
|
|
22
|
-
- jtcsv/nestjs
|
|
23
|
-
- jtcsv/remix
|
|
24
|
-
- jtcsv/nuxt
|
|
25
|
-
- jtcsv/sveltekit
|
|
26
|
-
- jtcsv/hono
|
|
27
|
-
- jtcsv/trpc
|
|
28
|
-
|
|
29
|
-
## Express
|
|
30
|
-
```bash
|
|
31
|
-
npm install @jtcsv/express-middleware express jtcsv
|
|
32
|
-
```
|
|
33
|
-
```javascript
|
|
34
|
-
const express = require('express');
|
|
35
|
-
const { middleware } = require('@jtcsv/express-middleware');
|
|
36
|
-
|
|
37
|
-
const app = express();
|
|
38
|
-
app.use(express.json());
|
|
39
|
-
app.use(express.text({ type: 'text/csv' }));
|
|
40
|
-
app.use(middleware());
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Fastify
|
|
44
|
-
```bash
|
|
45
|
-
npm install @jtcsv/fastify fastify fastify-plugin jtcsv
|
|
46
|
-
```
|
|
47
|
-
```javascript
|
|
48
|
-
const fastify = require('fastify')();
|
|
49
|
-
await fastify.register(require('@jtcsv/fastify'), { prefix: '/api' });
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Next.js
|
|
53
|
-
```bash
|
|
54
|
-
npm install @jtcsv/nextjs jtcsv
|
|
55
|
-
```
|
|
56
|
-
```javascript
|
|
57
|
-
import handler from '@jtcsv/nextjs/route';
|
|
58
|
-
export default handler;
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## NestJS
|
|
62
|
-
```bash
|
|
63
|
-
npm install @jtcsv/nestjs jtcsv
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Remix
|
|
67
|
-
```bash
|
|
68
|
-
npm install @jtcsv/remix jtcsv
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Nuxt
|
|
72
|
-
```bash
|
|
73
|
-
npm install @jtcsv/nuxt jtcsv
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## SvelteKit
|
|
77
|
-
```bash
|
|
78
|
-
npm install @jtcsv/sveltekit jtcsv
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Hono
|
|
82
|
-
```bash
|
|
83
|
-
npm install @jtcsv/hono jtcsv
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## tRPC
|
|
87
|
-
```bash
|
|
88
|
-
npm install @jtcsv/trpc jtcsv
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
See each package README in this folder for API details.
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
# @jtcsv/express-middleware
|
|
2
|
-
|
|
3
|
-
Express middleware that converts CSV/JSON payloads and exposes the converted data on `req.converted`.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
```bash
|
|
7
|
-
npm install @jtcsv/express-middleware express jtcsv
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
## Quick start
|
|
11
|
-
```javascript
|
|
12
|
-
const express = require('express');
|
|
13
|
-
const { middleware } = require('@jtcsv/express-middleware');
|
|
14
|
-
|
|
15
|
-
const app = express();
|
|
16
|
-
app.use(express.json());
|
|
17
|
-
app.use(express.text({ type: 'text/csv' }));
|
|
18
|
-
app.use(middleware());
|
|
19
|
-
|
|
20
|
-
app.post('/api/convert', (req, res) => {
|
|
21
|
-
res.json(req.converted);
|
|
22
|
-
});
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Options
|
|
26
|
-
The middleware detects input/output format based on `Content-Type`, `Accept`, and `?format=csv`.
|
|
27
|
-
|
|
28
|
-
```javascript
|
|
29
|
-
app.use(middleware({
|
|
30
|
-
autoDetect: true,
|
|
31
|
-
delimiter: ',',
|
|
32
|
-
enableFastPath: true,
|
|
33
|
-
preventCsvInjection: true,
|
|
34
|
-
rfc4180Compliant: true,
|
|
35
|
-
conversionOptions: {
|
|
36
|
-
parseNumbers: true,
|
|
37
|
-
parseBooleans: true
|
|
38
|
-
}
|
|
39
|
-
}));
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Note: body size limits are controlled by your body parser (for example `express.json({ limit: '10mb' })`).
|
|
43
|
-
|
|
44
|
-
## Helpers
|
|
45
|
-
```javascript
|
|
46
|
-
const {
|
|
47
|
-
csvToJsonRoute,
|
|
48
|
-
jsonToCsvRoute,
|
|
49
|
-
uploadCsvRoute,
|
|
50
|
-
healthCheck
|
|
51
|
-
} = require('@jtcsv/express-middleware');
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## req.converted shape
|
|
55
|
-
```json
|
|
56
|
-
{
|
|
57
|
-
"data": "...",
|
|
58
|
-
"format": "json",
|
|
59
|
-
"inputFormat": "csv",
|
|
60
|
-
"outputFormat": "json",
|
|
61
|
-
"stats": { "inputSize": 0, "outputSize": 0, "processingTime": 0, "conversion": "csv->json" },
|
|
62
|
-
"options": {}
|
|
63
|
-
}
|
|
64
|
-
```
|