jtcsv 2.1.3 → 2.2.2
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/LICENSE +1 -1
- package/README.md +60 -341
- package/bin/jtcsv.js +2462 -1372
- package/csv-to-json.js +35 -26
- package/dist/jtcsv.cjs.js +807 -133
- package/dist/jtcsv.cjs.js.map +1 -1
- package/dist/jtcsv.esm.js +800 -134
- package/dist/jtcsv.esm.js.map +1 -1
- package/dist/jtcsv.umd.js +807 -133
- package/dist/jtcsv.umd.js.map +1 -1
- package/errors.js +20 -0
- package/examples/browser-vanilla.html +37 -0
- package/examples/cli-batch-processing.js +38 -0
- package/examples/error-handling.js +324 -0
- package/examples/ndjson-processing.js +434 -0
- package/examples/react-integration.jsx +637 -0
- package/examples/schema-validation.js +640 -0
- package/examples/simple-usage.js +10 -7
- package/examples/typescript-example.ts +486 -0
- package/examples/web-workers-advanced.js +28 -0
- package/index.d.ts +2 -0
- package/json-save.js +2 -1
- package/json-to-csv.js +171 -131
- package/package.json +20 -4
- package/plugins/README.md +41 -467
- package/plugins/express-middleware/README.md +32 -274
- package/plugins/hono/README.md +16 -13
- package/plugins/nestjs/README.md +13 -11
- package/plugins/nextjs-api/README.md +28 -423
- package/plugins/nextjs-api/index.js +1 -2
- package/plugins/nextjs-api/route.js +1 -2
- package/plugins/nuxt/README.md +6 -7
- package/plugins/remix/README.md +9 -9
- package/plugins/sveltekit/README.md +8 -8
- package/plugins/trpc/README.md +8 -5
- package/src/browser/browser-functions.js +33 -3
- package/src/browser/csv-to-json-browser.js +269 -11
- package/src/browser/errors-browser.js +19 -1
- package/src/browser/index.js +39 -5
- package/src/browser/streams.js +393 -0
- package/src/browser/workers/csv-parser.worker.js +20 -2
- package/src/browser/workers/worker-pool.js +507 -447
- package/src/core/plugin-system.js +4 -0
- package/src/engines/fast-path-engine.js +31 -23
- package/src/errors.js +26 -0
- package/src/formats/ndjson-parser.js +54 -5
- package/src/formats/tsv-parser.js +4 -1
- package/src/utils/schema-validator.js +594 -0
- package/src/utils/transform-loader.js +205 -0
- package/src/web-server/index.js +683 -0
- package/stream-csv-to-json.js +16 -87
- package/stream-json-to-csv.js +18 -86
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transform Loader Utility
|
|
3
|
+
*
|
|
4
|
+
* Utility for loading and applying transform functions from JavaScript files
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const vm = require('vm');
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
ValidationError,
|
|
13
|
+
SecurityError,
|
|
14
|
+
ConfigurationError
|
|
15
|
+
} = require('../errors');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Validates transform function
|
|
19
|
+
* @private
|
|
20
|
+
*/
|
|
21
|
+
function validateTransformFunction(fn) {
|
|
22
|
+
if (typeof fn !== 'function') {
|
|
23
|
+
throw new ValidationError('Transform must export a function');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check function arity (should accept 1-2 parameters)
|
|
27
|
+
const functionString = fn.toString();
|
|
28
|
+
const paramMatch = functionString.match(/\(([^)]*)\)/);
|
|
29
|
+
if (paramMatch) {
|
|
30
|
+
const params = paramMatch[1].split(',').map(p => p.trim()).filter(p => p);
|
|
31
|
+
if (params.length === 0 || params.length > 2) {
|
|
32
|
+
throw new ValidationError('Transform function should accept 1-2 parameters: (row, index)');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Loads transform function from a JavaScript file
|
|
41
|
+
*
|
|
42
|
+
* @param {string} transformPath - Path to JavaScript file with transform function
|
|
43
|
+
* @returns {Function} Transform function
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* // transform.js
|
|
47
|
+
* module.exports = function(row, index) {
|
|
48
|
+
* return { ...row, processed: true, index };
|
|
49
|
+
* };
|
|
50
|
+
*
|
|
51
|
+
* // Usage
|
|
52
|
+
* const transform = loadTransform('./transform.js');
|
|
53
|
+
* const result = transform({ id: 1, name: 'John' }, 0);
|
|
54
|
+
*/
|
|
55
|
+
function loadTransform(transformPath) {
|
|
56
|
+
if (!transformPath || typeof transformPath !== 'string') {
|
|
57
|
+
throw new ValidationError('Transform path must be a string');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Validate file path
|
|
61
|
+
const safePath = path.resolve(transformPath);
|
|
62
|
+
|
|
63
|
+
// Prevent directory traversal
|
|
64
|
+
const normalizedPath = path.normalize(transformPath);
|
|
65
|
+
if (normalizedPath.includes('..') ||
|
|
66
|
+
/\\\.\.\\|\/\.\.\//.test(transformPath) ||
|
|
67
|
+
transformPath.startsWith('..') ||
|
|
68
|
+
transformPath.includes('/..')) {
|
|
69
|
+
throw new SecurityError('Directory traversal detected in transform file path');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check file exists and has .js extension
|
|
73
|
+
if (!fs.existsSync(safePath)) {
|
|
74
|
+
throw new ValidationError(`Transform file not found: ${transformPath}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!safePath.toLowerCase().endsWith('.js')) {
|
|
78
|
+
throw new ValidationError('Transform file must have .js extension');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Read and evaluate the transform file in a safe context
|
|
83
|
+
const transformCode = fs.readFileSync(safePath, 'utf8');
|
|
84
|
+
|
|
85
|
+
// Create a safe context with limited access
|
|
86
|
+
const sandbox = {
|
|
87
|
+
console,
|
|
88
|
+
require,
|
|
89
|
+
module: { exports: {} },
|
|
90
|
+
exports: {},
|
|
91
|
+
__filename: safePath,
|
|
92
|
+
__dirname: path.dirname(safePath),
|
|
93
|
+
Buffer,
|
|
94
|
+
process: {
|
|
95
|
+
env: process.env,
|
|
96
|
+
cwd: process.cwd,
|
|
97
|
+
platform: process.platform
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Create a context and run the code
|
|
102
|
+
const context = vm.createContext(sandbox);
|
|
103
|
+
const script = new vm.Script(transformCode, { filename: safePath });
|
|
104
|
+
script.runInContext(context);
|
|
105
|
+
|
|
106
|
+
// Get the exported function
|
|
107
|
+
const transformFn = context.module.exports || context.exports;
|
|
108
|
+
|
|
109
|
+
// Handle default export for ES6 modules
|
|
110
|
+
const finalTransform = transformFn.default || transformFn;
|
|
111
|
+
|
|
112
|
+
// Validate the transform function
|
|
113
|
+
validateTransformFunction(finalTransform);
|
|
114
|
+
|
|
115
|
+
return finalTransform;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error instanceof ValidationError || error instanceof SecurityError) {
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (error.code === 'EACCES') {
|
|
122
|
+
throw new SecurityError(`Permission denied reading transform file: ${transformPath}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
throw new ValidationError(`Failed to load transform function: ${error.message}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Creates a transform hook for use with csvToJson/jsonToCsv hooks system
|
|
131
|
+
*
|
|
132
|
+
* @param {string|Function} transform - Transform function or path to transform file
|
|
133
|
+
* @returns {Function} Transform hook function
|
|
134
|
+
*/
|
|
135
|
+
function createTransformHook(transform) {
|
|
136
|
+
let transformFn;
|
|
137
|
+
|
|
138
|
+
if (typeof transform === 'string') {
|
|
139
|
+
// Load transform from file
|
|
140
|
+
transformFn = loadTransform(transform);
|
|
141
|
+
} else if (typeof transform === 'function') {
|
|
142
|
+
// Use provided function
|
|
143
|
+
validateTransformFunction(transform);
|
|
144
|
+
transformFn = transform;
|
|
145
|
+
} else {
|
|
146
|
+
throw new ValidationError('Transform must be a function or a path to a JavaScript file');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Return a hook function compatible with hooks.perRow
|
|
150
|
+
return function(row, index, context) {
|
|
151
|
+
try {
|
|
152
|
+
return transformFn(row, index);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
// Log error but don't crash - return original row
|
|
155
|
+
console.error(`Transform error at row ${index}: ${error.message}`);
|
|
156
|
+
if (process.env.NODE_ENV === 'development') {
|
|
157
|
+
console.error(error.stack);
|
|
158
|
+
}
|
|
159
|
+
return row;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Applies transform to data array
|
|
166
|
+
*
|
|
167
|
+
* @param {Array} data - Array of data to transform
|
|
168
|
+
* @param {string|Function} transform - Transform function or path to transform file
|
|
169
|
+
* @returns {Array} Transformed data
|
|
170
|
+
*/
|
|
171
|
+
function applyTransform(data, transform) {
|
|
172
|
+
if (!Array.isArray(data)) {
|
|
173
|
+
throw new ValidationError('Data must be an array');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const transformHook = createTransformHook(transform);
|
|
177
|
+
|
|
178
|
+
return data.map((row, index) => {
|
|
179
|
+
return transformHook(row, index, { operation: 'applyTransform' });
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Creates a TransformHooks instance with transform function
|
|
185
|
+
*
|
|
186
|
+
* @param {string|Function} transform - Transform function or path to transform file
|
|
187
|
+
* @returns {TransformHooks} TransformHooks instance
|
|
188
|
+
*/
|
|
189
|
+
function createTransformHooksWithTransform(transform) {
|
|
190
|
+
const { TransformHooks } = require('../core/transform-hooks');
|
|
191
|
+
const hooks = new TransformHooks();
|
|
192
|
+
|
|
193
|
+
const transformHook = createTransformHook(transform);
|
|
194
|
+
hooks.perRow(transformHook);
|
|
195
|
+
|
|
196
|
+
return hooks;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = {
|
|
200
|
+
loadTransform,
|
|
201
|
+
createTransformHook,
|
|
202
|
+
applyTransform,
|
|
203
|
+
createTransformHooksWithTransform,
|
|
204
|
+
validateTransformFunction
|
|
205
|
+
};
|