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.
Files changed (52) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +60 -341
  3. package/bin/jtcsv.js +2462 -1372
  4. package/csv-to-json.js +35 -26
  5. package/dist/jtcsv.cjs.js +807 -133
  6. package/dist/jtcsv.cjs.js.map +1 -1
  7. package/dist/jtcsv.esm.js +800 -134
  8. package/dist/jtcsv.esm.js.map +1 -1
  9. package/dist/jtcsv.umd.js +807 -133
  10. package/dist/jtcsv.umd.js.map +1 -1
  11. package/errors.js +20 -0
  12. package/examples/browser-vanilla.html +37 -0
  13. package/examples/cli-batch-processing.js +38 -0
  14. package/examples/error-handling.js +324 -0
  15. package/examples/ndjson-processing.js +434 -0
  16. package/examples/react-integration.jsx +637 -0
  17. package/examples/schema-validation.js +640 -0
  18. package/examples/simple-usage.js +10 -7
  19. package/examples/typescript-example.ts +486 -0
  20. package/examples/web-workers-advanced.js +28 -0
  21. package/index.d.ts +2 -0
  22. package/json-save.js +2 -1
  23. package/json-to-csv.js +171 -131
  24. package/package.json +20 -4
  25. package/plugins/README.md +41 -467
  26. package/plugins/express-middleware/README.md +32 -274
  27. package/plugins/hono/README.md +16 -13
  28. package/plugins/nestjs/README.md +13 -11
  29. package/plugins/nextjs-api/README.md +28 -423
  30. package/plugins/nextjs-api/index.js +1 -2
  31. package/plugins/nextjs-api/route.js +1 -2
  32. package/plugins/nuxt/README.md +6 -7
  33. package/plugins/remix/README.md +9 -9
  34. package/plugins/sveltekit/README.md +8 -8
  35. package/plugins/trpc/README.md +8 -5
  36. package/src/browser/browser-functions.js +33 -3
  37. package/src/browser/csv-to-json-browser.js +269 -11
  38. package/src/browser/errors-browser.js +19 -1
  39. package/src/browser/index.js +39 -5
  40. package/src/browser/streams.js +393 -0
  41. package/src/browser/workers/csv-parser.worker.js +20 -2
  42. package/src/browser/workers/worker-pool.js +507 -447
  43. package/src/core/plugin-system.js +4 -0
  44. package/src/engines/fast-path-engine.js +31 -23
  45. package/src/errors.js +26 -0
  46. package/src/formats/ndjson-parser.js +54 -5
  47. package/src/formats/tsv-parser.js +4 -1
  48. package/src/utils/schema-validator.js +594 -0
  49. package/src/utils/transform-loader.js +205 -0
  50. package/src/web-server/index.js +683 -0
  51. package/stream-csv-to-json.js +16 -87
  52. 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
+ };