jtcsv 3.0.0 → 3.1.1

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 (258) hide show
  1. package/README.md +205 -146
  2. package/bin/jtcsv.ts +280 -202
  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 +1261 -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 +336 -7
  23. package/dist/jtcsv-core.cjs.js.map +1 -1
  24. package/dist/jtcsv-core.esm.js +336 -7
  25. package/dist/jtcsv-core.esm.js.map +1 -1
  26. package/dist/jtcsv-core.umd.js +336 -7
  27. package/dist/jtcsv-core.umd.js.map +1 -1
  28. package/dist/jtcsv-full.cjs.js +336 -7
  29. package/dist/jtcsv-full.cjs.js.map +1 -1
  30. package/dist/jtcsv-full.esm.js +336 -7
  31. package/dist/jtcsv-full.esm.js.map +1 -1
  32. package/dist/jtcsv-full.umd.js +336 -7
  33. package/dist/jtcsv-full.umd.js.map +1 -1
  34. package/dist/jtcsv-workers.esm.js +9 -0
  35. package/dist/jtcsv-workers.esm.js.map +1 -1
  36. package/dist/jtcsv-workers.umd.js +9 -0
  37. package/dist/jtcsv-workers.umd.js.map +1 -1
  38. package/dist/jtcsv.cjs.js +1998 -2092
  39. package/dist/jtcsv.cjs.js.map +1 -1
  40. package/dist/jtcsv.esm.js +1994 -2092
  41. package/dist/jtcsv.esm.js.map +1 -1
  42. package/dist/jtcsv.umd.js +2157 -2251
  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 +192 -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/{src → dist/src}/web-server/index.js +251 -286
  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 +664 -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 +2 -2
  131. package/examples/advanced/performance-optimization.ts +2 -2
  132. package/examples/cli-advanced-usage.md +2 -0
  133. package/examples/cli-tool.ts +1 -1
  134. package/examples/large-dataset-example.ts +2 -2
  135. package/examples/simple-usage.ts +2 -2
  136. package/examples/streaming-example.ts +1 -1
  137. package/index.d.ts +186 -15
  138. package/package.json +243 -305
  139. package/plugins.d.ts +37 -0
  140. package/schema.d.ts +103 -0
  141. package/src/browser/csv-to-json-browser.ts +233 -3
  142. package/src/browser/errors-browser.ts +45 -28
  143. package/src/browser/json-to-csv-browser.ts +81 -5
  144. package/src/browser/streams.ts +73 -6
  145. package/src/core/delimiter-cache.ts +21 -11
  146. package/src/core/plugin-system.ts +343 -155
  147. package/src/core/transform-hooks.ts +20 -12
  148. package/src/engines/fast-path-engine.ts +48 -32
  149. package/src/errors.ts +1 -72
  150. package/src/formats/ndjson-parser.ts +6 -0
  151. package/src/formats/tsv-parser.ts +6 -0
  152. package/src/types/index.ts +21 -1
  153. package/src/utils/validators.ts +35 -0
  154. package/src/web-server/index.ts +1 -1
  155. package/bin/jtcsv.js +0 -2532
  156. package/csv-to-json.js +0 -711
  157. package/errors.js +0 -394
  158. package/examples/advanced/conditional-transformations.js +0 -446
  159. package/examples/advanced/csv-parser.worker.js +0 -89
  160. package/examples/advanced/nested-objects-example.js +0 -306
  161. package/examples/advanced/performance-optimization.js +0 -504
  162. package/examples/advanced/run-demo-server.js +0 -116
  163. package/examples/cli-batch-processing.js +0 -38
  164. package/examples/cli-tool.js +0 -183
  165. package/examples/error-handling.js +0 -338
  166. package/examples/express-api.js +0 -164
  167. package/examples/large-dataset-example.js +0 -182
  168. package/examples/ndjson-processing.js +0 -434
  169. package/examples/plugin-excel-exporter.js +0 -406
  170. package/examples/schema-validation.js +0 -640
  171. package/examples/simple-usage.js +0 -282
  172. package/examples/streaming-example.js +0 -418
  173. package/examples/web-workers-advanced.js +0 -28
  174. package/index.js +0 -82
  175. package/json-save.js +0 -255
  176. package/json-to-csv.js +0 -668
  177. package/plugins/README.md +0 -91
  178. package/plugins/express-middleware/README.md +0 -83
  179. package/plugins/express-middleware/example.js +0 -135
  180. package/plugins/express-middleware/example.ts +0 -135
  181. package/plugins/express-middleware/index.d.ts +0 -114
  182. package/plugins/express-middleware/index.js +0 -512
  183. package/plugins/express-middleware/index.ts +0 -557
  184. package/plugins/express-middleware/package.json +0 -52
  185. package/plugins/fastify-plugin/index.js +0 -404
  186. package/plugins/fastify-plugin/index.ts +0 -443
  187. package/plugins/fastify-plugin/package.json +0 -55
  188. package/plugins/hono/README.md +0 -28
  189. package/plugins/hono/index.d.ts +0 -12
  190. package/plugins/hono/index.js +0 -36
  191. package/plugins/hono/index.ts +0 -226
  192. package/plugins/hono/package.json +0 -35
  193. package/plugins/nestjs/README.md +0 -35
  194. package/plugins/nestjs/index.d.ts +0 -25
  195. package/plugins/nestjs/index.js +0 -77
  196. package/plugins/nestjs/index.ts +0 -201
  197. package/plugins/nestjs/package.json +0 -37
  198. package/plugins/nextjs-api/README.md +0 -57
  199. package/plugins/nextjs-api/examples/ConverterComponent.jsx +0 -386
  200. package/plugins/nextjs-api/examples/ConverterComponent.tsx +0 -386
  201. package/plugins/nextjs-api/examples/api-convert.js +0 -67
  202. package/plugins/nextjs-api/examples/api-convert.ts +0 -67
  203. package/plugins/nextjs-api/index.js +0 -387
  204. package/plugins/nextjs-api/index.tsx +0 -339
  205. package/plugins/nextjs-api/package.json +0 -63
  206. package/plugins/nextjs-api/route.js +0 -370
  207. package/plugins/nextjs-api/route.ts +0 -370
  208. package/plugins/nuxt/README.md +0 -24
  209. package/plugins/nuxt/index.js +0 -21
  210. package/plugins/nuxt/index.ts +0 -94
  211. package/plugins/nuxt/package.json +0 -35
  212. package/plugins/nuxt/runtime/composables/useJtcsv.js +0 -6
  213. package/plugins/nuxt/runtime/composables/useJtcsv.ts +0 -100
  214. package/plugins/nuxt/runtime/plugin.js +0 -6
  215. package/plugins/nuxt/runtime/plugin.ts +0 -71
  216. package/plugins/remix/README.md +0 -26
  217. package/plugins/remix/index.d.ts +0 -16
  218. package/plugins/remix/index.js +0 -62
  219. package/plugins/remix/index.ts +0 -260
  220. package/plugins/remix/package.json +0 -35
  221. package/plugins/sveltekit/README.md +0 -28
  222. package/plugins/sveltekit/index.d.ts +0 -17
  223. package/plugins/sveltekit/index.js +0 -54
  224. package/plugins/sveltekit/index.ts +0 -301
  225. package/plugins/sveltekit/package.json +0 -33
  226. package/plugins/trpc/README.md +0 -25
  227. package/plugins/trpc/index.d.ts +0 -7
  228. package/plugins/trpc/index.js +0 -32
  229. package/plugins/trpc/index.ts +0 -267
  230. package/plugins/trpc/package.json +0 -34
  231. package/src/browser/browser-functions.js +0 -219
  232. package/src/browser/core.js +0 -92
  233. package/src/browser/csv-to-json-browser.js +0 -722
  234. package/src/browser/errors-browser.js +0 -212
  235. package/src/browser/extensions/plugins.js +0 -92
  236. package/src/browser/extensions/workers.js +0 -39
  237. package/src/browser/index.js +0 -113
  238. package/src/browser/json-to-csv-browser.js +0 -319
  239. package/src/browser/streams.js +0 -403
  240. package/src/browser/workers/csv-parser.worker.js +0 -377
  241. package/src/browser/workers/worker-pool.js +0 -527
  242. package/src/core/delimiter-cache.js +0 -200
  243. package/src/core/node-optimizations.js +0 -408
  244. package/src/core/plugin-system.js +0 -494
  245. package/src/core/transform-hooks.js +0 -350
  246. package/src/engines/fast-path-engine-new.js +0 -338
  247. package/src/engines/fast-path-engine.js +0 -844
  248. package/src/errors.js +0 -26
  249. package/src/formats/ndjson-parser.js +0 -467
  250. package/src/formats/tsv-parser.js +0 -339
  251. package/src/index-with-plugins.js +0 -378
  252. package/src/utils/bom-utils.js +0 -259
  253. package/src/utils/encoding-support.js +0 -124
  254. package/src/utils/schema-validator.js +0 -594
  255. package/src/utils/transform-loader.js +0 -205
  256. package/src/utils/zod-adapter.js +0 -170
  257. package/stream-csv-to-json.js +0 -560
  258. package/stream-json-to-csv.js +0 -465
@@ -1,594 +0,0 @@
1
- /**
2
- * Schema Validator Utility
3
- *
4
- * Utility for loading and applying JSON schema validation in CLI
5
- */
6
-
7
- const fs = require('fs');
8
- const path = require('path');
9
-
10
- const {
11
- ValidationError,
12
- SecurityError,
13
- ConfigurationError
14
- } = require('../errors');
15
-
16
- /**
17
- * Loads JSON schema from file or string
18
- *
19
- * @param {string} schemaPathOrJson - Path to JSON file or JSON string
20
- * @returns {Object} Parsed JSON schema
21
- */
22
- function loadSchema(schemaPathOrJson) {
23
- if (!schemaPathOrJson || typeof schemaPathOrJson !== 'string') {
24
- throw new ValidationError('Schema must be a string (JSON or file path)');
25
- }
26
-
27
- let schemaString = schemaPathOrJson;
28
-
29
- // Check if it's a file path (ends with .json or contains path separators)
30
- const isFilePath = schemaPathOrJson.endsWith('.json') ||
31
- schemaPathOrJson.includes('/') ||
32
- schemaPathOrJson.includes('\\');
33
-
34
- if (isFilePath) {
35
- // Validate file path
36
- const safePath = path.resolve(schemaPathOrJson);
37
-
38
- // Prevent directory traversal
39
- const normalizedPath = path.normalize(schemaPathOrJson);
40
- if (normalizedPath.includes('..') ||
41
- /\\\.\.\\|\/\.\.\//.test(schemaPathOrJson) ||
42
- schemaPathOrJson.startsWith('..') ||
43
- schemaPathOrJson.includes('/..')) {
44
- throw new SecurityError('Directory traversal detected in schema file path');
45
- }
46
-
47
- // Check file exists and has .json extension
48
- if (!fs.existsSync(safePath)) {
49
- throw new ValidationError(`Schema file not found: ${schemaPathOrJson}`);
50
- }
51
-
52
- if (!safePath.toLowerCase().endsWith('.json')) {
53
- throw new ValidationError('Schema file must have .json extension');
54
- }
55
-
56
- try {
57
- schemaString = fs.readFileSync(safePath, 'utf8');
58
- } catch (error) {
59
- if (error.code === 'EACCES') {
60
- throw new SecurityError(`Permission denied reading schema file: ${schemaPathOrJson}`);
61
- }
62
- throw new ValidationError(`Failed to read schema file: ${error.message}`);
63
- }
64
- }
65
-
66
- // Parse JSON schema
67
- try {
68
- const schema = JSON.parse(schemaString);
69
-
70
- // Validate basic schema structure
71
- if (typeof schema !== 'object' || schema === null) {
72
- throw new ValidationError('Schema must be a JSON object');
73
- }
74
-
75
- return schema;
76
- } catch (error) {
77
- if (error instanceof SyntaxError) {
78
- throw new ValidationError(`Invalid JSON in schema: ${error.message}`);
79
- }
80
- throw new ValidationError(`Failed to parse schema: ${error.message}`);
81
- }
82
- }
83
-
84
- /**
85
- * Creates a validation hook for use with csvToJson/jsonToCsv hooks system
86
- *
87
- * @param {string|Object} schema - Schema object or path to schema file
88
- * @returns {Function} Validation hook function
89
- */
90
- function createValidationHook(schema) {
91
- let schemaObj;
92
-
93
- if (typeof schema === 'string') {
94
- // Load schema from file or JSON string
95
- schemaObj = loadSchema(schema);
96
- } else if (typeof schema === 'object' && schema !== null) {
97
- // Use provided schema object
98
- schemaObj = schema;
99
- } else {
100
- throw new ValidationError('Schema must be an object or a path to a JSON file');
101
- }
102
-
103
- // Try to use @jtcsv/validator if available
104
- let validator;
105
- try {
106
- const JtcsvValidator = require('../../packages/jtcsv-validator/src/index');
107
- validator = new JtcsvValidator();
108
-
109
- // Convert simple schema format to validator format
110
- if (schemaObj.fields) {
111
- // Assume it's already in validator format
112
- validator.schema(schemaObj.fields);
113
- } else {
114
- // Convert simple field definitions
115
- Object.entries(schemaObj).forEach(([field, rule]) => {
116
- if (typeof rule === 'object') {
117
- validator.field(field, rule);
118
- }
119
- });
120
- }
121
- } catch (error) {
122
- // Fallback to simple validation if validator is not available
123
- console.warn('@jtcsv/validator not available, using simple validation');
124
- validator = createSimpleValidator(schemaObj);
125
- }
126
-
127
- // Return a hook function compatible with hooks.perRow
128
- return function (row, index, context) {
129
- try {
130
- const result = validator.validate([row], {
131
- stopOnFirstError: true,
132
- transform: false
133
- });
134
-
135
- if (!result.valid && result.errors.length > 0) {
136
- const error = result.errors[0];
137
- throw new ValidationError(
138
- `Row ${index + 1}: ${error.message} (field: ${error.field})`
139
- );
140
- }
141
-
142
- return row;
143
- } catch (error) {
144
- if (error instanceof ValidationError) {
145
- throw error;
146
- }
147
- // Log error but don't crash - return original row
148
- console.error(`Validation error at row ${index}: ${error.message}`);
149
- if (process.env.NODE_ENV === 'development') {
150
- console.error(error.stack);
151
- }
152
- return row;
153
- }
154
- };
155
- }
156
-
157
- /**
158
- * Creates a simple validator for fallback when @jtcsv/validator is not available
159
- *
160
- * @private
161
- */
162
- function createSimpleValidator(schema) {
163
- return {
164
- validate(data, options = {}) {
165
- const errors = [];
166
- const warnings = [];
167
-
168
- if (!Array.isArray(data)) {
169
- return {
170
- valid: false,
171
- errors: [{ type: 'INVALID_DATA', message: 'Data must be an array' }],
172
- warnings: [],
173
- summary: {
174
- totalRows: 0,
175
- validRows: 0,
176
- errorCount: 1,
177
- warningCount: 0
178
- }
179
- };
180
- }
181
-
182
- for (let i = 0; i < data.length; i++) {
183
- const row = data[i];
184
-
185
- for (const [field, rule] of Object.entries(schema)) {
186
- const value = row[field];
187
-
188
- // Check required
189
- if (rule.required && (value === undefined || value === null || value === '')) {
190
- errors.push({
191
- row: i + 1,
192
- type: 'REQUIRED',
193
- field,
194
- message: `Field "${field}" is required`,
195
- value
196
- });
197
- continue;
198
- }
199
-
200
- // Skip further validation if value is empty and not required
201
- if (value === undefined || value === null || value === '') {
202
- continue;
203
- }
204
-
205
- // Check type
206
- if (rule.type) {
207
- const types = Array.isArray(rule.type) ? rule.type : [rule.type];
208
- let typeValid = false;
209
-
210
- for (const type of types) {
211
- if (checkType(value, type)) {
212
- typeValid = true;
213
- break;
214
- }
215
- }
216
-
217
- if (!typeValid) {
218
- errors.push({
219
- row: i + 1,
220
- type: 'TYPE',
221
- field,
222
- message: `Field "${field}" must be of type ${types.join(' or ')}`,
223
- value,
224
- expected: types
225
- });
226
- }
227
- }
228
-
229
- // Check min/max for strings
230
- if (rule.min !== undefined && typeof value === 'string' && value.length < rule.min) {
231
- errors.push({
232
- row: i + 1,
233
- type: 'MIN_LENGTH',
234
- field,
235
- message: `Field "${field}" must be at least ${rule.min} characters`,
236
- value,
237
- min: rule.min
238
- });
239
- }
240
-
241
- if (rule.max !== undefined && typeof value === 'string' && value.length > rule.max) {
242
- errors.push({
243
- row: i + 1,
244
- type: 'MAX_LENGTH',
245
- field,
246
- message: `Field "${field}" must be at most ${rule.max} characters`,
247
- value,
248
- max: rule.max
249
- });
250
- }
251
-
252
- // Check min/max for numbers
253
- if (rule.min !== undefined && typeof value === 'number' && value < rule.min) {
254
- errors.push({
255
- row: i + 1,
256
- type: 'MIN_VALUE',
257
- field,
258
- message: `Field "${field}" must be at least ${rule.min}`,
259
- value,
260
- min: rule.min
261
- });
262
- }
263
-
264
- if (rule.max !== undefined && typeof value === 'number' && value > rule.max) {
265
- errors.push({
266
- row: i + 1,
267
- type: 'MAX_VALUE',
268
- field,
269
- message: `Field "${field}" must be at most ${rule.max}`,
270
- value,
271
- max: rule.max
272
- });
273
- }
274
-
275
- // Check pattern
276
- if (rule.pattern && typeof value === 'string') {
277
- const pattern = rule.pattern instanceof RegExp ? rule.pattern : new RegExp(rule.pattern);
278
- if (!pattern.test(value)) {
279
- errors.push({
280
- row: i + 1,
281
- type: 'PATTERN',
282
- field,
283
- message: `Field "${field}" must match pattern`,
284
- value,
285
- pattern: pattern.toString()
286
- });
287
- }
288
- }
289
-
290
- // Check enum
291
- if (rule.enum && Array.isArray(rule.enum) && !rule.enum.includes(value)) {
292
- errors.push({
293
- row: i + 1,
294
- type: 'ENUM',
295
- field,
296
- message: `Field "${field}" must be one of: ${rule.enum.join(', ')}`,
297
- value,
298
- allowed: rule.enum
299
- });
300
- }
301
- }
302
- }
303
-
304
- return {
305
- valid: errors.length === 0,
306
- errors,
307
- warnings,
308
- summary: {
309
- totalRows: data.length,
310
- validRows: data.length - errors.length,
311
- errorCount: errors.length,
312
- warningCount: warnings.length
313
- }
314
- };
315
- }
316
- };
317
- }
318
-
319
- /**
320
- * Checks if value matches type
321
- *
322
- * @private
323
- */
324
- function checkType(value, type) {
325
- switch (type) {
326
- case 'string':
327
- return typeof value === 'string';
328
- case 'number':
329
- return typeof value === 'number' && !isNaN(value);
330
- case 'boolean':
331
- return typeof value === 'boolean';
332
- case 'integer':
333
- return Number.isInteger(value);
334
- case 'float':
335
- return typeof value === 'number' && !Number.isInteger(value);
336
- case 'date':
337
- return value instanceof Date && !isNaN(value);
338
- case 'array':
339
- return Array.isArray(value);
340
- case 'object':
341
- return typeof value === 'object' && value !== null && !Array.isArray(value);
342
- default:
343
- return false;
344
- }
345
- }
346
-
347
- /**
348
- * Applies schema validation to data array
349
- *
350
- * @param {Array} data - Array of data to validate
351
- * @param {string|Object} schema - Schema object or path to schema file
352
- * @returns {Object} Validation result
353
- */
354
- function applySchemaValidation(data, schema) {
355
- if (!Array.isArray(data)) {
356
- throw new ValidationError('Data must be an array');
357
- }
358
-
359
- const validationHook = createValidationHook(schema);
360
- const errors = [];
361
- const validatedData = [];
362
-
363
- for (let i = 0; i < data.length; i++) {
364
- try {
365
- const validatedRow = validationHook(data[i], i, { operation: 'validate' });
366
- validatedData.push(validatedRow);
367
- } catch (error) {
368
- if (error instanceof ValidationError) {
369
- errors.push({
370
- row: i + 1,
371
- message: error.message,
372
- data: data[i]
373
- });
374
- } else {
375
- // Skip rows with non-validation errors
376
- validatedData.push(data[i]);
377
- }
378
- }
379
- }
380
-
381
- return {
382
- valid: errors.length === 0,
383
- errors,
384
- data: validatedData,
385
- summary: {
386
- totalRows: data.length,
387
- validRows: validatedData.length,
388
- errorCount: errors.length,
389
- errorRate: data.length > 0 ? (errors.length / data.length) * 100 : 0
390
- }
391
- };
392
- }
393
-
394
- /**
395
- * Creates a TransformHooks instance with validation
396
- *
397
- * @param {string|Object} schema - Schema object or path to schema file
398
- * @returns {Object} TransformHooks instance
399
- */
400
- function createValidationHooks(schema) {
401
- const { TransformHooks } = require('../core/transform-hooks');
402
- const hooks = new TransformHooks();
403
-
404
- const validationHook = createValidationHook(schema);
405
- hooks.perRow(validationHook);
406
-
407
- return hooks;
408
- }
409
-
410
- /**
411
- * Creates schema validators from JSON schema
412
- *
413
- * @param {Object} schema - JSON schema
414
- * @returns {Object} Validators object
415
- */
416
- function createSchemaValidators(schema) {
417
- const validators = {};
418
-
419
- // Handle both JSON Schema format and simple format
420
- const properties = schema.properties || schema;
421
- const requiredFields = schema.required || [];
422
-
423
- if (!properties || typeof properties !== 'object') {
424
- return validators;
425
- }
426
-
427
- for (const [key, definition] of Object.entries(properties)) {
428
- const validator = {
429
- type: definition.type,
430
- required: requiredFields.includes(key)
431
- };
432
-
433
- // Add format function for dates and other formats
434
- if (definition.type === 'string' && definition.format) {
435
- validator.format = (value) => {
436
- // Handle date-time format
437
- if (definition.format === 'date-time') {
438
- if (value instanceof Date) {
439
- return value.toISOString();
440
- }
441
- /* istanbul ignore next */
442
- if (typeof value === 'string') {
443
- // Try to parse as date
444
- const date = new Date(value);
445
- if (!isNaN(date.getTime())) {
446
- return date.toISOString();
447
- }
448
- }
449
- }
450
- // Handle email format
451
- if (definition.format === 'email') {
452
- if (typeof value === 'string') {
453
- return value.toLowerCase().trim();
454
- }
455
- }
456
- // Handle uri format
457
- if (definition.format === 'uri') {
458
- if (typeof value === 'string') {
459
- return value.trim();
460
- }
461
- }
462
- return value;
463
- };
464
- }
465
-
466
- // Add validation function
467
- validator.validate = (value) => {
468
- if (value === null || value === undefined) {
469
- return !validator.required;
470
- }
471
-
472
- // Type validation
473
- if (definition.type === 'string' && typeof value !== 'string') {
474
- // For date-time format, also accept Date objects
475
- if (definition.format === 'date-time' && value instanceof Date) {
476
- return true;
477
- }
478
- return false;
479
- }
480
- if (definition.type === 'number' && typeof value !== 'number') {
481
- return false;
482
- }
483
- if (definition.type === 'integer' && (!Number.isInteger(value) || typeof value !== 'number')) {
484
- return false;
485
- }
486
- if (definition.type === 'boolean' && typeof value !== 'boolean') {
487
- return false;
488
- }
489
- if (definition.type === 'array' && !Array.isArray(value)) {
490
- return false;
491
- }
492
- if (definition.type === 'object' && (typeof value !== 'object' || value === null || Array.isArray(value))) {
493
- return false;
494
- }
495
-
496
- // Additional constraints for strings
497
- if (definition.type === 'string') {
498
- if (definition.minLength !== undefined && value.length < definition.minLength) {
499
- return false;
500
- }
501
- if (definition.maxLength !== undefined && value.length > definition.maxLength) {
502
- return false;
503
- }
504
- if (definition.pattern && !new RegExp(definition.pattern).test(value)) {
505
- return false;
506
- }
507
- if (definition.format === 'email' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
508
- return false;
509
- }
510
- if (definition.format === 'uri') {
511
- try {
512
- new URL(value);
513
- } catch {
514
- return false;
515
- }
516
- }
517
- }
518
-
519
- // Additional constraints for numbers
520
- if (definition.type === 'number' || definition.type === 'integer') {
521
- if (definition.minimum !== undefined && value < definition.minimum) {
522
- return false;
523
- }
524
- if (definition.maximum !== undefined && value > definition.maximum) {
525
- return false;
526
- }
527
- if (definition.exclusiveMinimum !== undefined && value <= definition.exclusiveMinimum) {
528
- return false;
529
- }
530
- if (definition.exclusiveMaximum !== undefined && value >= definition.exclusiveMaximum) {
531
- return false;
532
- }
533
- if (definition.multipleOf !== undefined && value % definition.multipleOf !== 0) {
534
- return false;
535
- }
536
- }
537
-
538
- // Additional constraints for arrays
539
- if (definition.type === 'array') {
540
- if (definition.minItems !== undefined && value.length < definition.minItems) {
541
- return false;
542
- }
543
- if (definition.maxItems !== undefined && value.length > definition.maxItems) {
544
- return false;
545
- }
546
- if (definition.uniqueItems && new Set(value).size !== value.length) {
547
- return false;
548
- }
549
- // Validate array items if schema is provided
550
- if (definition.items) {
551
- for (const item of value) {
552
- const itemValidator = createSchemaValidators({ properties: { item: definition.items } });
553
- if (itemValidator.item && !itemValidator.item.validate(item)) {
554
- return false;
555
- }
556
- }
557
- }
558
- }
559
-
560
- // Additional constraints for objects
561
- if (definition.type === 'object' && definition.properties) {
562
- const nestedValidators = createSchemaValidators(definition);
563
- for (const [nestedKey, nestedValidator] of Object.entries(nestedValidators)) {
564
- if (value[nestedKey] !== undefined && !nestedValidator.validate(value[nestedKey])) {
565
- return false;
566
- }
567
- if (nestedValidator.required && value[nestedKey] === undefined) {
568
- return false;
569
- }
570
- }
571
- }
572
-
573
- // Check enum
574
- if (definition.enum && !definition.enum.includes(value)) {
575
- return false;
576
- }
577
-
578
- return true;
579
- };
580
-
581
- validators[key] = validator;
582
- }
583
-
584
- return validators;
585
- }
586
-
587
- module.exports = {
588
- loadSchema,
589
- createValidationHook,
590
- applySchemaValidation,
591
- createValidationHooks,
592
- checkType,
593
- createSchemaValidators // Add this line
594
- };