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.
Files changed (246) hide show
  1. package/README.md +204 -115
  2. package/bin/jtcsv.ts +2612 -0
  3. package/browser.d.ts +142 -0
  4. package/dist/benchmark.js +446 -0
  5. package/dist/benchmark.js.map +1 -0
  6. package/dist/bin/jtcsv.js +1940 -0
  7. package/dist/bin/jtcsv.js.map +1 -0
  8. package/dist/csv-to-json.js +1262 -0
  9. package/dist/csv-to-json.js.map +1 -0
  10. package/dist/errors.js +291 -0
  11. package/dist/errors.js.map +1 -0
  12. package/dist/eslint.config.js +147 -0
  13. package/dist/eslint.config.js.map +1 -0
  14. package/dist/index-core.js +95 -0
  15. package/dist/index-core.js.map +1 -0
  16. package/dist/index.js +93 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/json-save.js +229 -0
  19. package/dist/json-save.js.map +1 -0
  20. package/dist/json-to-csv.js +576 -0
  21. package/dist/json-to-csv.js.map +1 -0
  22. package/dist/jtcsv-core.cjs.js +1736 -0
  23. package/dist/jtcsv-core.cjs.js.map +1 -0
  24. package/dist/jtcsv-core.esm.js +1708 -0
  25. package/dist/jtcsv-core.esm.js.map +1 -0
  26. package/dist/jtcsv-core.umd.js +1742 -0
  27. package/dist/jtcsv-core.umd.js.map +1 -0
  28. package/dist/jtcsv-full.cjs.js +2241 -0
  29. package/dist/jtcsv-full.cjs.js.map +1 -0
  30. package/dist/jtcsv-full.esm.js +2209 -0
  31. package/dist/jtcsv-full.esm.js.map +1 -0
  32. package/dist/jtcsv-full.umd.js +2247 -0
  33. package/dist/jtcsv-full.umd.js.map +1 -0
  34. package/dist/jtcsv-workers.esm.js +768 -0
  35. package/dist/jtcsv-workers.esm.js.map +1 -0
  36. package/dist/jtcsv-workers.umd.js +782 -0
  37. package/dist/jtcsv-workers.umd.js.map +1 -0
  38. package/dist/jtcsv.cjs.js +1996 -2048
  39. package/dist/jtcsv.cjs.js.map +1 -1
  40. package/dist/jtcsv.esm.js +1992 -2048
  41. package/dist/jtcsv.esm.js.map +1 -1
  42. package/dist/jtcsv.umd.js +2157 -2209
  43. package/dist/jtcsv.umd.js.map +1 -1
  44. package/dist/plugins/express-middleware/index.js +350 -0
  45. package/dist/plugins/express-middleware/index.js.map +1 -0
  46. package/dist/plugins/fastify-plugin/index.js +315 -0
  47. package/dist/plugins/fastify-plugin/index.js.map +1 -0
  48. package/dist/plugins/hono/index.js +111 -0
  49. package/dist/plugins/hono/index.js.map +1 -0
  50. package/dist/plugins/nestjs/index.js +112 -0
  51. package/dist/plugins/nestjs/index.js.map +1 -0
  52. package/dist/plugins/nuxt/index.js +53 -0
  53. package/dist/plugins/nuxt/index.js.map +1 -0
  54. package/dist/plugins/remix/index.js +133 -0
  55. package/dist/plugins/remix/index.js.map +1 -0
  56. package/dist/plugins/sveltekit/index.js +155 -0
  57. package/dist/plugins/sveltekit/index.js.map +1 -0
  58. package/dist/plugins/trpc/index.js +136 -0
  59. package/dist/plugins/trpc/index.js.map +1 -0
  60. package/dist/run-demo.js +49 -0
  61. package/dist/run-demo.js.map +1 -0
  62. package/dist/src/browser/browser-functions.js +193 -0
  63. package/dist/src/browser/browser-functions.js.map +1 -0
  64. package/dist/src/browser/core.js +123 -0
  65. package/dist/src/browser/core.js.map +1 -0
  66. package/dist/src/browser/csv-to-json-browser.js +353 -0
  67. package/dist/src/browser/csv-to-json-browser.js.map +1 -0
  68. package/dist/src/browser/errors-browser.js +219 -0
  69. package/dist/src/browser/errors-browser.js.map +1 -0
  70. package/dist/src/browser/extensions/plugins.js +106 -0
  71. package/dist/src/browser/extensions/plugins.js.map +1 -0
  72. package/dist/src/browser/extensions/workers.js +66 -0
  73. package/dist/src/browser/extensions/workers.js.map +1 -0
  74. package/dist/src/browser/index.js +140 -0
  75. package/dist/src/browser/index.js.map +1 -0
  76. package/dist/src/browser/json-to-csv-browser.js +225 -0
  77. package/dist/src/browser/json-to-csv-browser.js.map +1 -0
  78. package/dist/src/browser/streams.js +340 -0
  79. package/dist/src/browser/streams.js.map +1 -0
  80. package/dist/src/browser/workers/csv-parser.worker.js +264 -0
  81. package/dist/src/browser/workers/csv-parser.worker.js.map +1 -0
  82. package/dist/src/browser/workers/worker-pool.js +338 -0
  83. package/dist/src/browser/workers/worker-pool.js.map +1 -0
  84. package/dist/src/core/delimiter-cache.js +196 -0
  85. package/dist/src/core/delimiter-cache.js.map +1 -0
  86. package/dist/src/core/node-optimizations.js +279 -0
  87. package/dist/src/core/node-optimizations.js.map +1 -0
  88. package/dist/src/core/plugin-system.js +399 -0
  89. package/dist/src/core/plugin-system.js.map +1 -0
  90. package/dist/src/core/transform-hooks.js +348 -0
  91. package/dist/src/core/transform-hooks.js.map +1 -0
  92. package/dist/src/engines/fast-path-engine-new.js +262 -0
  93. package/dist/src/engines/fast-path-engine-new.js.map +1 -0
  94. package/dist/src/engines/fast-path-engine.js +671 -0
  95. package/dist/src/engines/fast-path-engine.js.map +1 -0
  96. package/dist/src/errors.js +18 -0
  97. package/dist/src/errors.js.map +1 -0
  98. package/dist/src/formats/ndjson-parser.js +332 -0
  99. package/dist/src/formats/ndjson-parser.js.map +1 -0
  100. package/dist/src/formats/tsv-parser.js +230 -0
  101. package/dist/src/formats/tsv-parser.js.map +1 -0
  102. package/dist/src/index-with-plugins.js +259 -0
  103. package/dist/src/index-with-plugins.js.map +1 -0
  104. package/dist/src/types/index.js +3 -0
  105. package/dist/src/types/index.js.map +1 -0
  106. package/dist/src/utils/bom-utils.js +267 -0
  107. package/dist/src/utils/bom-utils.js.map +1 -0
  108. package/dist/src/utils/encoding-support.js +77 -0
  109. package/dist/src/utils/encoding-support.js.map +1 -0
  110. package/dist/src/utils/schema-validator.js +609 -0
  111. package/dist/src/utils/schema-validator.js.map +1 -0
  112. package/dist/src/utils/transform-loader.js +281 -0
  113. package/dist/src/utils/transform-loader.js.map +1 -0
  114. package/dist/src/utils/validators.js +40 -0
  115. package/dist/src/utils/validators.js.map +1 -0
  116. package/dist/src/utils/zod-adapter.js +144 -0
  117. package/dist/src/utils/zod-adapter.js.map +1 -0
  118. package/dist/src/web-server/index.js +648 -0
  119. package/dist/src/web-server/index.js.map +1 -0
  120. package/dist/src/workers/csv-multithreaded.js +211 -0
  121. package/dist/src/workers/csv-multithreaded.js.map +1 -0
  122. package/dist/src/workers/csv-parser.worker.js +179 -0
  123. package/dist/src/workers/csv-parser.worker.js.map +1 -0
  124. package/dist/src/workers/worker-pool.js +228 -0
  125. package/dist/src/workers/worker-pool.js.map +1 -0
  126. package/dist/stream-csv-to-json.js +665 -0
  127. package/dist/stream-csv-to-json.js.map +1 -0
  128. package/dist/stream-json-to-csv.js +389 -0
  129. package/dist/stream-json-to-csv.js.map +1 -0
  130. package/examples/advanced/conditional-transformations.ts +446 -0
  131. package/examples/advanced/csv-parser.worker.ts +89 -0
  132. package/examples/advanced/nested-objects-example.ts +306 -0
  133. package/examples/advanced/performance-optimization.ts +504 -0
  134. package/examples/advanced/run-demo-server.ts +116 -0
  135. package/examples/advanced/web-worker-usage.html +874 -0
  136. package/examples/async-multithreaded-example.ts +335 -0
  137. package/examples/cli-advanced-usage.md +290 -0
  138. package/examples/{cli-batch-processing.js → cli-batch-processing.ts} +38 -38
  139. package/examples/{cli-tool.js → cli-tool.ts} +5 -8
  140. package/examples/{error-handling.js → error-handling.ts} +356 -324
  141. package/examples/{express-api.js → express-api.ts} +161 -164
  142. package/examples/{large-dataset-example.js → large-dataset-example.ts} +201 -182
  143. package/examples/{ndjson-processing.js → ndjson-processing.ts} +456 -434
  144. package/examples/{plugin-excel-exporter.js → plugin-excel-exporter.ts} +6 -7
  145. package/examples/react-integration.tsx +637 -0
  146. package/examples/{schema-validation.js → schema-validation.ts} +2 -2
  147. package/examples/simple-usage.ts +194 -0
  148. package/examples/{streaming-example.js → streaming-example.ts} +12 -12
  149. package/index.d.ts +187 -18
  150. package/package.json +75 -81
  151. package/plugins.d.ts +37 -0
  152. package/schema.d.ts +103 -0
  153. package/src/browser/browser-functions.ts +402 -0
  154. package/src/browser/core.ts +152 -0
  155. package/src/browser/csv-to-json-browser.d.ts +3 -0
  156. package/src/browser/csv-to-json-browser.ts +494 -0
  157. package/src/browser/{errors-browser.js → errors-browser.ts} +305 -197
  158. package/src/browser/extensions/plugins.ts +93 -0
  159. package/src/browser/extensions/workers.ts +39 -0
  160. package/src/browser/globals.d.ts +5 -0
  161. package/src/browser/index.ts +192 -0
  162. package/src/browser/json-to-csv-browser.d.ts +3 -0
  163. package/src/browser/json-to-csv-browser.ts +338 -0
  164. package/src/browser/streams.ts +403 -0
  165. package/src/browser/workers/{csv-parser.worker.js → csv-parser.worker.ts} +3 -3
  166. package/src/browser/workers/{worker-pool.js → worker-pool.ts} +51 -30
  167. package/src/core/delimiter-cache.ts +320 -0
  168. package/src/core/{node-optimizations.js → node-optimizations.ts} +448 -407
  169. package/src/core/plugin-system.ts +588 -0
  170. package/src/core/transform-hooks.ts +566 -0
  171. package/src/engines/{fast-path-engine-new.js → fast-path-engine-new.ts} +11 -2
  172. package/src/engines/{fast-path-engine.js → fast-path-engine.ts} +79 -53
  173. package/src/errors.ts +1 -0
  174. package/src/formats/{ndjson-parser.js → ndjson-parser.ts} +24 -16
  175. package/src/formats/{tsv-parser.js → tsv-parser.ts} +18 -17
  176. package/src/{index-with-plugins.js → index-with-plugins.ts} +381 -357
  177. package/src/types/index.ts +275 -0
  178. package/src/utils/bom-utils.ts +373 -0
  179. package/src/utils/encoding-support.ts +155 -0
  180. package/src/utils/{schema-validator.js → schema-validator.ts} +814 -589
  181. package/src/utils/transform-loader.ts +389 -0
  182. package/src/utils/validators.ts +35 -0
  183. package/src/utils/zod-adapter.ts +280 -0
  184. package/src/web-server/{index.js → index.ts} +19 -19
  185. package/src/workers/csv-multithreaded.ts +310 -0
  186. package/src/workers/csv-parser.worker.ts +227 -0
  187. package/src/workers/worker-pool.ts +409 -0
  188. package/bin/jtcsv.js +0 -2462
  189. package/csv-to-json.js +0 -688
  190. package/errors.js +0 -208
  191. package/examples/simple-usage.js +0 -282
  192. package/index.js +0 -68
  193. package/json-save.js +0 -254
  194. package/json-to-csv.js +0 -526
  195. package/plugins/README.md +0 -91
  196. package/plugins/express-middleware/README.md +0 -64
  197. package/plugins/express-middleware/example.js +0 -136
  198. package/plugins/express-middleware/index.d.ts +0 -114
  199. package/plugins/express-middleware/index.js +0 -360
  200. package/plugins/express-middleware/package.json +0 -52
  201. package/plugins/fastify-plugin/index.js +0 -406
  202. package/plugins/fastify-plugin/package.json +0 -55
  203. package/plugins/hono/README.md +0 -28
  204. package/plugins/hono/index.d.ts +0 -12
  205. package/plugins/hono/index.js +0 -36
  206. package/plugins/hono/package.json +0 -35
  207. package/plugins/nestjs/README.md +0 -35
  208. package/plugins/nestjs/index.d.ts +0 -25
  209. package/plugins/nestjs/index.js +0 -77
  210. package/plugins/nestjs/package.json +0 -37
  211. package/plugins/nextjs-api/README.md +0 -57
  212. package/plugins/nextjs-api/examples/ConverterComponent.jsx +0 -386
  213. package/plugins/nextjs-api/examples/api-convert.js +0 -69
  214. package/plugins/nextjs-api/index.js +0 -387
  215. package/plugins/nextjs-api/package.json +0 -63
  216. package/plugins/nextjs-api/route.js +0 -371
  217. package/plugins/nuxt/README.md +0 -24
  218. package/plugins/nuxt/index.js +0 -21
  219. package/plugins/nuxt/package.json +0 -35
  220. package/plugins/nuxt/runtime/composables/useJtcsv.js +0 -6
  221. package/plugins/nuxt/runtime/plugin.js +0 -6
  222. package/plugins/remix/README.md +0 -26
  223. package/plugins/remix/index.d.ts +0 -16
  224. package/plugins/remix/index.js +0 -62
  225. package/plugins/remix/package.json +0 -35
  226. package/plugins/sveltekit/README.md +0 -28
  227. package/plugins/sveltekit/index.d.ts +0 -17
  228. package/plugins/sveltekit/index.js +0 -54
  229. package/plugins/sveltekit/package.json +0 -33
  230. package/plugins/trpc/README.md +0 -25
  231. package/plugins/trpc/index.d.ts +0 -7
  232. package/plugins/trpc/index.js +0 -32
  233. package/plugins/trpc/package.json +0 -34
  234. package/src/browser/browser-functions.js +0 -219
  235. package/src/browser/csv-to-json-browser.js +0 -700
  236. package/src/browser/index.js +0 -113
  237. package/src/browser/json-to-csv-browser.js +0 -309
  238. package/src/browser/streams.js +0 -393
  239. package/src/core/delimiter-cache.js +0 -186
  240. package/src/core/plugin-system.js +0 -476
  241. package/src/core/transform-hooks.js +0 -350
  242. package/src/errors.js +0 -26
  243. package/src/utils/transform-loader.js +0 -205
  244. package/stream-csv-to-json.js +0 -542
  245. package/stream-json-to-csv.js +0 -464
  246. /package/examples/{web-workers-advanced.js → web-workers-advanced.ts} +0 -0
@@ -0,0 +1,389 @@
1
+ /**
2
+ * Transform Loader Utility
3
+ *
4
+ * Utility for loading and applying transform functions from JavaScript files
5
+ */
6
+
7
+ import * as fs from 'fs';
8
+ import * as fsPromises from 'fs/promises';
9
+ import * as path from 'path';
10
+ import * as vm from 'vm';
11
+ import {
12
+ ValidationError,
13
+ SecurityError,
14
+ ConfigurationError
15
+ } from '../errors';
16
+
17
+ /**
18
+ * Validates transform function
19
+ */
20
+ function validateTransformFunction(fn: Function): boolean {
21
+ if (typeof fn !== 'function') {
22
+ throw new ValidationError('Transform must export a function');
23
+ }
24
+
25
+ // Check function arity (should accept 1-2 parameters)
26
+ const functionString = fn.toString();
27
+ const paramMatch = functionString.match(/\(([^)]*)\)/);
28
+ if (paramMatch) {
29
+ const params = paramMatch[1].split(',').map(p => p.trim()).filter(p => p);
30
+ if (params.length === 0 || params.length > 2) {
31
+ throw new ValidationError('Transform function should accept 1-2 parameters: (row, index)');
32
+ }
33
+ }
34
+
35
+ return true;
36
+ }
37
+
38
+ /**
39
+ * Loads transform function from a JavaScript file
40
+ *
41
+ * @param transformPath - Path to JavaScript file with transform function
42
+ * @returns Transform function
43
+ *
44
+ * @example
45
+ * // transform.js
46
+ * module.exports = function(row, index) {
47
+ * return { ...row, processed: true, index };
48
+ * };
49
+ *
50
+ * // Usage
51
+ * const transform = loadTransform('./transform.js');
52
+ * const result = transform({ id: 1, name: 'John' }, 0);
53
+ */
54
+ export function loadTransform(transformPath: string): Function {
55
+ if (!transformPath || typeof transformPath !== 'string') {
56
+ throw new ValidationError('Transform path must be a string');
57
+ }
58
+
59
+ // Validate file path
60
+ const safePath = path.resolve(transformPath);
61
+
62
+ // Prevent directory traversal
63
+ const normalizedPath = path.normalize(transformPath);
64
+ if (normalizedPath.includes('..') ||
65
+ /\\\.\.\\|\/\.\.\//.test(transformPath) ||
66
+ transformPath.startsWith('..') ||
67
+ transformPath.includes('/..')) {
68
+ throw new SecurityError('Directory traversal detected in transform file path');
69
+ }
70
+
71
+ // Check file exists and has .js extension
72
+ if (!fs.existsSync(safePath)) {
73
+ throw new ValidationError(`Transform file not found: ${transformPath}`);
74
+ }
75
+
76
+ if (!safePath.toLowerCase().endsWith('.js')) {
77
+ throw new ValidationError('Transform file must have .js extension');
78
+ }
79
+
80
+ try {
81
+ // Read and evaluate the transform file in a safe context
82
+ const transformCode = fs.readFileSync(safePath, 'utf8');
83
+
84
+ // Create a safe context with limited access
85
+ const sandbox = {
86
+ console,
87
+ require,
88
+ module: { exports: {} },
89
+ exports: {},
90
+ __filename: safePath,
91
+ __dirname: path.dirname(safePath),
92
+ Buffer,
93
+ process: {
94
+ env: process.env,
95
+ cwd: process.cwd,
96
+ platform: process.platform
97
+ }
98
+ };
99
+
100
+ // Create a context and run the code
101
+ const context = vm.createContext(sandbox);
102
+ const script = new vm.Script(transformCode, { filename: safePath });
103
+ script.runInContext(context);
104
+
105
+ // Get the exported function
106
+ const transformFn = (context as any).module.exports || (context as any).exports;
107
+
108
+ // Handle default export for ES6 modules
109
+ const finalTransform = transformFn.default || transformFn;
110
+
111
+ // Validate the transform function
112
+ validateTransformFunction(finalTransform);
113
+
114
+ return finalTransform;
115
+ } catch (error: any) {
116
+ if (error instanceof ValidationError || error instanceof SecurityError) {
117
+ throw error;
118
+ }
119
+
120
+ if (error.code === 'EACCES') {
121
+ throw new SecurityError(`Permission denied reading transform file: ${transformPath}`);
122
+ }
123
+
124
+ throw new ValidationError(`Failed to load transform function: ${error.message}`);
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Creates a transform hook for use with csvToJson/jsonToCsv hooks system
130
+ *
131
+ * @param transform - Transform function or path to transform file
132
+ * @returns Transform hook function
133
+ */
134
+ export function createTransformHook(transform: string | Function): (row: any, index: number, context: any) => any {
135
+ let transformFn: Function;
136
+
137
+ if (typeof transform === 'string') {
138
+ // Load transform from file
139
+ transformFn = loadTransform(transform);
140
+ } else if (typeof transform === 'function') {
141
+ // Use provided function
142
+ validateTransformFunction(transform);
143
+ transformFn = transform;
144
+ } else {
145
+ throw new ValidationError('Transform must be a function or a path to a JavaScript file');
146
+ }
147
+
148
+ // Return a hook function compatible with hooks.perRow
149
+ return function (row: any, index: number, context: any): any {
150
+ try {
151
+ return transformFn(row, index);
152
+ } catch (error: any) {
153
+ // Log error but don't crash - return original row
154
+ console.error(`Transform error at row ${index}: ${error.message}`);
155
+ if (process.env['NODE_ENV'] === 'development') {
156
+ console.error(error.stack);
157
+ }
158
+ return row;
159
+ }
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Applies transform to data array
165
+ *
166
+ * @param data - Array of data to transform
167
+ * @param transform - Transform function or path to transform file
168
+ * @returns Transformed data
169
+ */
170
+ export function applyTransform(data: any[], transform: string | Function): any[] {
171
+ if (!Array.isArray(data)) {
172
+ throw new ValidationError('Data must be an array');
173
+ }
174
+
175
+ const transformHook = createTransformHook(transform);
176
+
177
+ return data.map((row, index) => {
178
+ return transformHook(row, index, { operation: 'applyTransform' });
179
+ });
180
+ }
181
+
182
+ /**
183
+ * Creates a TransformHooks instance with transform function
184
+ *
185
+ * @param transform - Transform function or path to transform file
186
+ * @returns TransformHooks instance
187
+ */
188
+ export function createTransformHooksWithTransform(transform: string | Function): any {
189
+ const { TransformHooks } = require('../core/transform-hooks');
190
+ const hooks = new TransformHooks();
191
+
192
+ const transformHook = createTransformHook(transform);
193
+ hooks.perRow(transformHook);
194
+
195
+ return hooks;
196
+ }
197
+
198
+ /**
199
+ * Async version of loadTransform that reads file asynchronously
200
+ *
201
+ * @param transformPath - Path to JavaScript file with transform function
202
+ * @returns Promise with transform function
203
+ */
204
+ export async function loadTransformAsync(transformPath: string): Promise<Function> {
205
+ if (!transformPath || typeof transformPath !== 'string') {
206
+ throw new ValidationError('Transform path must be a string');
207
+ }
208
+
209
+ // Validate file path
210
+ const safePath = path.resolve(transformPath);
211
+
212
+ // Prevent directory traversal
213
+ const normalizedPath = path.normalize(transformPath);
214
+ if (normalizedPath.includes('..') ||
215
+ /\\\.\.\\|\/\.\.\//.test(transformPath) ||
216
+ transformPath.startsWith('..') ||
217
+ transformPath.includes('/..')) {
218
+ throw new SecurityError('Directory traversal detected in transform file path');
219
+ }
220
+
221
+ // Check file exists and has .js extension
222
+ try {
223
+ await fsPromises.access(safePath);
224
+ } catch {
225
+ throw new ValidationError(`Transform file not found: ${transformPath}`);
226
+ }
227
+
228
+ if (!safePath.toLowerCase().endsWith('.js')) {
229
+ throw new ValidationError('Transform file must have .js extension');
230
+ }
231
+
232
+ try {
233
+ // Read and evaluate the transform file in a safe context
234
+ const transformCode = await fsPromises.readFile(safePath, 'utf8');
235
+
236
+ // Create a safe context with limited access
237
+ const sandbox = {
238
+ console,
239
+ require,
240
+ module: { exports: {} },
241
+ exports: {},
242
+ __filename: safePath,
243
+ __dirname: path.dirname(safePath),
244
+ Buffer,
245
+ process: {
246
+ env: process.env,
247
+ cwd: process.cwd,
248
+ platform: process.platform
249
+ }
250
+ };
251
+
252
+ // Create a context and run the code
253
+ const context = vm.createContext(sandbox);
254
+ const script = new vm.Script(transformCode, { filename: safePath });
255
+ script.runInContext(context);
256
+
257
+ // Get the exported function
258
+ const transformFn = (context as any).module.exports || (context as any).exports;
259
+
260
+ // Handle default export for ES6 modules
261
+ const finalTransform = transformFn.default || transformFn;
262
+
263
+ // Validate the transform function
264
+ validateTransformFunction(finalTransform);
265
+
266
+ return finalTransform;
267
+ } catch (error: any) {
268
+ if (error instanceof ValidationError || error instanceof SecurityError) {
269
+ throw error;
270
+ }
271
+
272
+ if (error.code === 'EACCES') {
273
+ throw new SecurityError(`Permission denied reading transform file: ${transformPath}`);
274
+ }
275
+
276
+ throw new ValidationError(`Failed to load transform function: ${error.message}`);
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Async version of applyTransform that uses worker threads for parallel transformation
282
+ *
283
+ * @param data - Array of data to transform
284
+ * @param transform - Transform function or path to transform file
285
+ * @returns Promise with transformed data
286
+ */
287
+ export async function applyTransformAsync(data: any[], transform: string | Function): Promise<any[]> {
288
+ if (!Array.isArray(data)) {
289
+ throw new ValidationError('Data must be an array');
290
+ }
291
+
292
+ // For large datasets, use worker pool
293
+ if (data.length > 1000) {
294
+ const { createWorkerPool } = require('../workers/worker-pool');
295
+ const pool = createWorkerPool({
296
+ workerCount: Math.min(4, require('os').cpus().length),
297
+ workerScript: require.resolve('./transform-worker.js')
298
+ });
299
+
300
+ try {
301
+ // Load transform function
302
+ const transformFn = typeof transform === 'string'
303
+ ? await loadTransformAsync(transform)
304
+ : transform;
305
+
306
+ // Execute transforms in parallel
307
+ const transformPromises = data.map((row, index) =>
308
+ pool.execute({ row, index, transform: transformFn.toString() })
309
+ );
310
+
311
+ const results = await Promise.all(transformPromises);
312
+ return results.map(result => result.transformedRow);
313
+ } finally {
314
+ await pool.terminate();
315
+ }
316
+ }
317
+
318
+ // For small datasets, transform synchronously
319
+ return applyTransform(data, transform);
320
+ }
321
+
322
+ /**
323
+ * Creates an async transform hook that can be used with async hooks
324
+ *
325
+ * @param transform - Transform function or path to transform file
326
+ * @returns Async transform hook function
327
+ */
328
+ export function createAsyncTransformHook(transform: string | Function): (row: any, index: number, context: any) => Promise<any> {
329
+ const syncHook = createTransformHook(transform);
330
+
331
+ return async function (row: any, index: number, context: any): Promise<any> {
332
+ return Promise.resolve(syncHook(row, index, context));
333
+ };
334
+ }
335
+
336
+ /**
337
+ * Validates that a transform function can be safely executed
338
+ *
339
+ * @param transformFn - Transform function to validate
340
+ * @returns Validation result
341
+ */
342
+ export function validateTransformSafety(transformFn: Function): { safe: boolean; issues: string[] } {
343
+ const issues: string[] = [];
344
+
345
+ // Check for dangerous patterns
346
+ const functionString = transformFn.toString().toLowerCase();
347
+
348
+ const dangerousPatterns = [
349
+ 'eval(',
350
+ 'new function',
351
+ 'settimeout',
352
+ 'setinterval',
353
+ 'process.exit',
354
+ 'require(',
355
+ 'fs.',
356
+ 'child_process',
357
+ 'exec(',
358
+ 'spawn(',
359
+ 'vm.run'
360
+ ];
361
+
362
+ for (const pattern of dangerousPatterns) {
363
+ if (functionString.includes(pattern)) {
364
+ issues.push(`Potentially dangerous pattern detected: ${pattern}`);
365
+ }
366
+ }
367
+
368
+ // Check for infinite loops
369
+ if (functionString.includes('while(true)') || functionString.includes('for(;;)')) {
370
+ issues.push('Potential infinite loop detected');
371
+ }
372
+
373
+ return {
374
+ safe: issues.length === 0,
375
+ issues
376
+ };
377
+ }
378
+
379
+ export default {
380
+ loadTransform,
381
+ loadTransformAsync,
382
+ createTransformHook,
383
+ createAsyncTransformHook,
384
+ applyTransform,
385
+ applyTransformAsync,
386
+ createTransformHooksWithTransform,
387
+ validateTransformFunction,
388
+ validateTransformSafety
389
+ };
@@ -0,0 +1,35 @@
1
+ export function isEmail(value: string): boolean {
2
+ if (typeof value !== 'string') {
3
+ return false;
4
+ }
5
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim());
6
+ }
7
+
8
+ export function isUrl(value: string): boolean {
9
+ if (typeof value !== 'string') {
10
+ return false;
11
+ }
12
+ try {
13
+ new URL(value);
14
+ return true;
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ export function isDate(value: string | Date): boolean {
21
+ if (value instanceof Date) {
22
+ return !isNaN(value.getTime());
23
+ }
24
+ if (typeof value !== 'string') {
25
+ return false;
26
+ }
27
+ const date = new Date(value);
28
+ return !isNaN(date.getTime());
29
+ }
30
+
31
+ export const validators = {
32
+ isEmail,
33
+ isUrl,
34
+ isDate
35
+ };
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Zod adapter for JTCSV schema validation.
3
+ *
4
+ * Provides integration with Zod schemas for CSV validation.
5
+ *
6
+ * @example
7
+ * const { z } = require('zod');
8
+ * const { createZodValidationHook } = require('./zod-adapter');
9
+ *
10
+ * const schema = z.object({
11
+ * name: z.string().min(1),
12
+ * age: z.number().int().min(0).max(150),
13
+ * email: z.string().email()
14
+ * });
15
+ *
16
+ * const validationHook = createZodValidationHook(schema);
17
+ *
18
+ * // Use with csvToJson
19
+ * const data = await csvToJson(csv, {
20
+ * hooks: { perRow: validationHook }
21
+ * });
22
+ */
23
+
24
+ import { ValidationError } from '../errors';
25
+
26
+ // Conditional imports for optional dependencies
27
+ type ZodSchema = any;
28
+ type YupSchema = any;
29
+
30
+ export interface ZodValidationOptions {
31
+ coerce?: boolean;
32
+ mode?: 'strict' | 'collect';
33
+ }
34
+
35
+ export interface YupValidationOptions {
36
+ abortEarly?: boolean;
37
+ stripUnknown?: boolean;
38
+ }
39
+
40
+ export interface ValidatedParserOptions {
41
+ library?: 'zod' | 'yup';
42
+ [key: string]: any;
43
+ }
44
+
45
+ export type RowHook = (row: any, index: number, context: any) => any | Promise<any>;
46
+
47
+ /**
48
+ * Creates a validation hook from a Zod schema.
49
+ *
50
+ * @param zodSchema - Zod schema instance
51
+ * @param options - Validation options
52
+ * @param options.coerce - Whether to coerce values according to Zod's coerce (default: true)
53
+ * @param options.mode - 'strict' (throw on first error) or 'collect' (collect all errors)
54
+ * @returns Validation hook compatible with JTCSV hooks.perRow
55
+ */
56
+ export function createZodValidationHook(
57
+ zodSchema: ZodSchema,
58
+ options: ZodValidationOptions = {}
59
+ ): RowHook {
60
+ const { coerce = true, mode = 'strict' } = options;
61
+
62
+ // Check if Zod is available
63
+ let zod: any;
64
+ try {
65
+ zod = require('zod');
66
+ } catch (error) {
67
+ throw new Error(
68
+ 'Zod is not installed. Please install zod: npm install zod'
69
+ );
70
+ }
71
+
72
+ // Ensure the passed schema is a Zod schema
73
+ if (!zodSchema || typeof zodSchema.safeParse !== 'function') {
74
+ throw new ValidationError('Provided schema is not a valid Zod schema');
75
+ }
76
+
77
+ // Return hook function
78
+ return function (row: any, index: number, context: any): any {
79
+ try {
80
+ const result = zodSchema.safeParse(row);
81
+
82
+ if (!result.success) {
83
+ const errors = result.error.errors;
84
+ const firstError = errors[0];
85
+ const path = firstError.path?.join('.') || '';
86
+ const message = firstError.message;
87
+
88
+ if (mode === 'strict') {
89
+ throw new ValidationError(
90
+ `Row ${index + 1}: ${path ? `Field "${path}": ` : ''}${message}`
91
+ );
92
+ } else {
93
+ // In collect mode, we attach errors to row metadata
94
+ // For simplicity, we still throw but could be extended
95
+ console.warn(`Row ${index + 1}: ${path ? `Field "${path}": ` : ''}${message}`);
96
+ return row;
97
+ }
98
+ }
99
+
100
+ // Return validated (and possibly coerced) data
101
+ return result.data;
102
+ } catch (error: any) {
103
+ if (error instanceof ValidationError) {
104
+ throw error;
105
+ }
106
+ // Unexpected error - log and return original row
107
+ console.error(`Zod validation error at row ${index}: ${error.message}`);
108
+ if (process.env['NODE_ENV'] === 'development') {
109
+ console.error(error.stack);
110
+ }
111
+ return row;
112
+ }
113
+ };
114
+ }
115
+
116
+ /**
117
+ * Creates a Yup validation hook.
118
+ *
119
+ * @param yupSchema - Yup schema instance
120
+ * @param options - Validation options
121
+ * @returns Validation hook
122
+ */
123
+ export function createYupValidationHook(
124
+ yupSchema: YupSchema,
125
+ options: YupValidationOptions = {}
126
+ ): RowHook {
127
+ const { abortEarly = false, stripUnknown = true } = options;
128
+
129
+ // Check if Yup is available
130
+ let yup: any;
131
+ try {
132
+ yup = require('yup');
133
+ } catch (error) {
134
+ throw new Error(
135
+ 'Yup is not installed. Please install yup: npm install yup'
136
+ );
137
+ }
138
+
139
+ if (!yupSchema || typeof yupSchema.validate !== 'function') {
140
+ throw new ValidationError('Provided schema is not a valid Yup schema');
141
+ }
142
+
143
+ return async function (row: any, index: number, context: any): Promise<any> {
144
+ try {
145
+ const validated = await yupSchema.validate(row, { abortEarly, stripUnknown });
146
+ return validated;
147
+ } catch (error: any) {
148
+ if (error.name === 'ValidationError') {
149
+ throw new ValidationError(`Row ${index + 1}: ${error.message}`);
150
+ }
151
+ console.error(`Yup validation error at row ${index}: ${error.message}`);
152
+ return row;
153
+ }
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Higher-order function that creates a csvToJson wrapper with schema validation.
159
+ *
160
+ * @param schema - Zod or Yup schema
161
+ * @param adapterOptions - Adapter-specific options
162
+ * @returns Function that takes csv and options, returns validated data
163
+ */
164
+ export function createValidatedParser(
165
+ schema: ZodSchema | YupSchema,
166
+ adapterOptions: ValidatedParserOptions = {}
167
+ ): (csv: string, parseOptions?: any) => Promise<any[]> {
168
+ const { library = 'zod', ...options } = adapterOptions;
169
+
170
+ let validationHook: RowHook;
171
+ if (library === 'zod') {
172
+ validationHook = createZodValidationHook(schema as ZodSchema, options);
173
+ } else if (library === 'yup') {
174
+ validationHook = createYupValidationHook(schema as YupSchema, options);
175
+ } else {
176
+ throw new ValidationError(`Unsupported validation library: ${library}`);
177
+ }
178
+
179
+ return async function (csv: string, parseOptions: any = {}): Promise<any[]> {
180
+ const { csvToJson } = require('../index');
181
+ const hooks = parseOptions.hooks || {};
182
+ // Merge validation hook with existing perRow hook
183
+ const existingPerRow = hooks.perRow;
184
+ hooks.perRow = function (row: any, index: number, context: any): any {
185
+ let validated = row;
186
+ if (existingPerRow) {
187
+ validated = existingPerRow(validated, index, context);
188
+ }
189
+ return validationHook(validated, index, context);
190
+ };
191
+
192
+ return csvToJson(csv, { ...parseOptions, hooks });
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Async version of createValidatedParser that uses worker threads for validation.
198
+ *
199
+ * @param schema - Zod or Yup schema
200
+ * @param adapterOptions - Adapter-specific options
201
+ * @returns Async function that validates CSV data in parallel
202
+ */
203
+ export function createAsyncValidatedParser(
204
+ schema: ZodSchema | YupSchema,
205
+ adapterOptions: ValidatedParserOptions = {}
206
+ ): (csv: string, parseOptions?: any) => Promise<any[]> {
207
+ const { library = 'zod', ...options } = adapterOptions;
208
+
209
+ return async function (csv: string, parseOptions: any = {}): Promise<any[]> {
210
+ const { csvToJson } = require('../index');
211
+ const { createWorkerPool } = require('../workers/worker-pool');
212
+
213
+ // Create worker pool for parallel validation
214
+ const pool = createWorkerPool({
215
+ workerCount: Math.min(4, require('os').cpus().length),
216
+ workerScript: require.resolve('./validation-worker.js')
217
+ });
218
+
219
+ try {
220
+ // Parse CSV without validation first
221
+ const data = await csvToJson(csv, parseOptions);
222
+
223
+ // Validate in parallel using worker pool
224
+ const validationPromises = data.map((row: any, index: number) =>
225
+ pool.execute({ row, index, schema, library, options })
226
+ );
227
+
228
+ const validatedRows = await Promise.all(validationPromises);
229
+ return validatedRows;
230
+ } finally {
231
+ await pool.terminate();
232
+ }
233
+ };
234
+ }
235
+
236
+ /**
237
+ * Creates a validation hook that works asynchronously with Zod schemas.
238
+ *
239
+ * @param zodSchema - Zod schema instance
240
+ * @param options - Validation options
241
+ * @returns Async validation hook
242
+ */
243
+ export function createAsyncZodValidationHook(
244
+ zodSchema: ZodSchema,
245
+ options: ZodValidationOptions = {}
246
+ ): RowHook {
247
+ const hook = createZodValidationHook(zodSchema, options);
248
+
249
+ return async function (row: any, index: number, context: any): Promise<any> {
250
+ // For async compatibility, wrap in Promise
251
+ return Promise.resolve(hook(row, index, context));
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Creates a validation hook that works asynchronously with Yup schemas.
257
+ *
258
+ * @param yupSchema - Yup schema instance
259
+ * @param options - Validation options
260
+ * @returns Async validation hook
261
+ */
262
+ export function createAsyncYupValidationHook(
263
+ yupSchema: YupSchema,
264
+ options: YupValidationOptions = {}
265
+ ): RowHook {
266
+ const hook = createYupValidationHook(yupSchema, options);
267
+
268
+ return async function (row: any, index: number, context: any): Promise<any> {
269
+ return hook(row, index, context);
270
+ };
271
+ }
272
+
273
+ export default {
274
+ createZodValidationHook,
275
+ createYupValidationHook,
276
+ createValidatedParser,
277
+ createAsyncValidatedParser,
278
+ createAsyncZodValidationHook,
279
+ createAsyncYupValidationHook
280
+ };