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
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadSchema = loadSchema;
|
|
37
|
+
exports.createValidationHook = createValidationHook;
|
|
38
|
+
exports.applySchemaValidation = applySchemaValidation;
|
|
39
|
+
exports.createValidationHooks = createValidationHooks;
|
|
40
|
+
exports.createSchemaValidators = createSchemaValidators;
|
|
41
|
+
exports.loadSchemaAsync = loadSchemaAsync;
|
|
42
|
+
exports.applySchemaValidationAsync = applySchemaValidationAsync;
|
|
43
|
+
exports.createAsyncValidationHook = createAsyncValidationHook;
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const fsPromises = __importStar(require("fs/promises"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const errors_1 = require("../errors");
|
|
48
|
+
function loadSchema(schemaPathOrJson) {
|
|
49
|
+
if (!schemaPathOrJson || typeof schemaPathOrJson !== 'string') {
|
|
50
|
+
throw new errors_1.ValidationError('Schema must be a string (JSON or file path)');
|
|
51
|
+
}
|
|
52
|
+
let schemaString = schemaPathOrJson;
|
|
53
|
+
const isFilePath = schemaPathOrJson.endsWith('.json') ||
|
|
54
|
+
schemaPathOrJson.includes('/') ||
|
|
55
|
+
schemaPathOrJson.includes('\\');
|
|
56
|
+
if (isFilePath) {
|
|
57
|
+
const safePath = path.resolve(schemaPathOrJson);
|
|
58
|
+
const normalizedPath = path.normalize(schemaPathOrJson);
|
|
59
|
+
if (normalizedPath.includes('..') ||
|
|
60
|
+
/\\\.\.\\|\/\.\.\//.test(schemaPathOrJson) ||
|
|
61
|
+
schemaPathOrJson.startsWith('..') ||
|
|
62
|
+
schemaPathOrJson.includes('/..')) {
|
|
63
|
+
throw new errors_1.SecurityError('Directory traversal detected in schema file path');
|
|
64
|
+
}
|
|
65
|
+
if (!fs.existsSync(safePath)) {
|
|
66
|
+
throw new errors_1.ValidationError(`Schema file not found: ${schemaPathOrJson}`);
|
|
67
|
+
}
|
|
68
|
+
if (!safePath.toLowerCase().endsWith('.json')) {
|
|
69
|
+
throw new errors_1.ValidationError('Schema file must have .json extension');
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
schemaString = fs.readFileSync(safePath, 'utf8');
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error.code === 'EACCES') {
|
|
76
|
+
throw new errors_1.SecurityError(`Permission denied reading schema file: ${schemaPathOrJson}`);
|
|
77
|
+
}
|
|
78
|
+
throw new errors_1.ValidationError(`Failed to read schema file: ${error.message}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const schema = JSON.parse(schemaString);
|
|
83
|
+
if (typeof schema !== 'object' || schema === null) {
|
|
84
|
+
throw new errors_1.ValidationError('Schema must be a JSON object');
|
|
85
|
+
}
|
|
86
|
+
return schema;
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error instanceof SyntaxError) {
|
|
90
|
+
throw new errors_1.ValidationError(`Invalid JSON in schema: ${error.message}`);
|
|
91
|
+
}
|
|
92
|
+
throw new errors_1.ValidationError(`Failed to parse schema: ${error.message}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function createSimpleValidator(schema) {
|
|
96
|
+
return {
|
|
97
|
+
validate(data, options = {}) {
|
|
98
|
+
const errors = [];
|
|
99
|
+
const warnings = [];
|
|
100
|
+
if (!Array.isArray(data)) {
|
|
101
|
+
return {
|
|
102
|
+
valid: false,
|
|
103
|
+
errors: [{
|
|
104
|
+
row: 0,
|
|
105
|
+
type: 'INVALID_DATA',
|
|
106
|
+
field: '',
|
|
107
|
+
message: 'Data must be an array'
|
|
108
|
+
}],
|
|
109
|
+
warnings: [],
|
|
110
|
+
summary: {
|
|
111
|
+
totalRows: 0,
|
|
112
|
+
validRows: 0,
|
|
113
|
+
errorCount: 1,
|
|
114
|
+
warningCount: 0
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
for (let i = 0; i < data.length; i++) {
|
|
119
|
+
const row = data[i];
|
|
120
|
+
for (const [field, rule] of Object.entries(schema)) {
|
|
121
|
+
const value = row[field];
|
|
122
|
+
if (rule.required && (value === undefined || value === null || value === '')) {
|
|
123
|
+
errors.push({
|
|
124
|
+
row: i + 1,
|
|
125
|
+
type: 'REQUIRED',
|
|
126
|
+
field,
|
|
127
|
+
message: `Field "${field}" is required`,
|
|
128
|
+
value
|
|
129
|
+
});
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (value === undefined || value === null || value === '') {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (rule.type) {
|
|
136
|
+
const types = Array.isArray(rule.type) ? rule.type : [rule.type];
|
|
137
|
+
let typeValid = false;
|
|
138
|
+
for (const type of types) {
|
|
139
|
+
if (checkType(value, type)) {
|
|
140
|
+
typeValid = true;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (!typeValid) {
|
|
145
|
+
errors.push({
|
|
146
|
+
row: i + 1,
|
|
147
|
+
type: 'TYPE',
|
|
148
|
+
field,
|
|
149
|
+
message: `Field "${field}" must be of type ${types.join(' or ')}`,
|
|
150
|
+
value,
|
|
151
|
+
expected: types
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (rule.min !== undefined && typeof value === 'string' && value.length < rule.min) {
|
|
156
|
+
errors.push({
|
|
157
|
+
row: i + 1,
|
|
158
|
+
type: 'MIN_LENGTH',
|
|
159
|
+
field,
|
|
160
|
+
message: `Field "${field}" must be at least ${rule.min} characters`,
|
|
161
|
+
value,
|
|
162
|
+
min: rule.min
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
if (rule.max !== undefined && typeof value === 'string' && value.length > rule.max) {
|
|
166
|
+
errors.push({
|
|
167
|
+
row: i + 1,
|
|
168
|
+
type: 'MAX_LENGTH',
|
|
169
|
+
field,
|
|
170
|
+
message: `Field "${field}" must be at most ${rule.max} characters`,
|
|
171
|
+
value,
|
|
172
|
+
max: rule.max
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (rule.min !== undefined && typeof value === 'number' && value < rule.min) {
|
|
176
|
+
errors.push({
|
|
177
|
+
row: i + 1,
|
|
178
|
+
type: 'MIN_VALUE',
|
|
179
|
+
field,
|
|
180
|
+
message: `Field "${field}" must be at least ${rule.min}`,
|
|
181
|
+
value,
|
|
182
|
+
min: rule.min
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (rule.max !== undefined && typeof value === 'number' && value > rule.max) {
|
|
186
|
+
errors.push({
|
|
187
|
+
row: i + 1,
|
|
188
|
+
type: 'MAX_VALUE',
|
|
189
|
+
field,
|
|
190
|
+
message: `Field "${field}" must be at most ${rule.max}`,
|
|
191
|
+
value,
|
|
192
|
+
max: rule.max
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (rule.pattern && typeof value === 'string') {
|
|
196
|
+
const pattern = rule.pattern instanceof RegExp ? rule.pattern : new RegExp(rule.pattern);
|
|
197
|
+
if (!pattern.test(value)) {
|
|
198
|
+
errors.push({
|
|
199
|
+
row: i + 1,
|
|
200
|
+
type: 'PATTERN',
|
|
201
|
+
field,
|
|
202
|
+
message: `Field "${field}" must match pattern`,
|
|
203
|
+
value,
|
|
204
|
+
pattern: pattern.toString()
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (rule.enum && Array.isArray(rule.enum) && !rule.enum.includes(value)) {
|
|
209
|
+
errors.push({
|
|
210
|
+
row: i + 1,
|
|
211
|
+
type: 'ENUM',
|
|
212
|
+
field,
|
|
213
|
+
message: `Field "${field}" must be one of: ${rule.enum.join(', ')}`,
|
|
214
|
+
value,
|
|
215
|
+
allowed: rule.enum
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
valid: errors.length === 0,
|
|
222
|
+
errors,
|
|
223
|
+
warnings,
|
|
224
|
+
summary: {
|
|
225
|
+
totalRows: data.length,
|
|
226
|
+
validRows: data.length - errors.length,
|
|
227
|
+
errorCount: errors.length,
|
|
228
|
+
warningCount: warnings.length
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function checkType(value, type) {
|
|
235
|
+
switch (type) {
|
|
236
|
+
case 'string':
|
|
237
|
+
return typeof value === 'string';
|
|
238
|
+
case 'number':
|
|
239
|
+
return typeof value === 'number' && !isNaN(value);
|
|
240
|
+
case 'boolean':
|
|
241
|
+
return typeof value === 'boolean';
|
|
242
|
+
case 'integer':
|
|
243
|
+
return Number.isInteger(value);
|
|
244
|
+
case 'float':
|
|
245
|
+
return typeof value === 'number' && !Number.isInteger(value);
|
|
246
|
+
case 'date':
|
|
247
|
+
return value instanceof Date && !isNaN(value.getTime());
|
|
248
|
+
case 'array':
|
|
249
|
+
return Array.isArray(value);
|
|
250
|
+
case 'object':
|
|
251
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
252
|
+
default:
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function createValidationHook(schema) {
|
|
257
|
+
let schemaObj;
|
|
258
|
+
if (typeof schema === 'string') {
|
|
259
|
+
schemaObj = loadSchema(schema);
|
|
260
|
+
}
|
|
261
|
+
else if (typeof schema === 'object' && schema !== null) {
|
|
262
|
+
schemaObj = schema;
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
throw new errors_1.ValidationError('Schema must be an object or a path to a JSON file');
|
|
266
|
+
}
|
|
267
|
+
let validator;
|
|
268
|
+
try {
|
|
269
|
+
const JtcsvValidator = require('../../packages/jtcsv-validator/src/index');
|
|
270
|
+
validator = new JtcsvValidator();
|
|
271
|
+
if (schemaObj.fields) {
|
|
272
|
+
validator.schema(schemaObj.fields);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
Object.entries(schemaObj).forEach(([field, rule]) => {
|
|
276
|
+
if (typeof rule === 'object') {
|
|
277
|
+
validator.field(field, rule);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
console.warn('@jtcsv/validator not available, using simple validation');
|
|
284
|
+
validator = createSimpleValidator(schemaObj);
|
|
285
|
+
}
|
|
286
|
+
return function (row, index, context) {
|
|
287
|
+
try {
|
|
288
|
+
const result = validator.validate([row], {
|
|
289
|
+
stopOnFirstError: true,
|
|
290
|
+
transform: false
|
|
291
|
+
});
|
|
292
|
+
if (!result.valid && result.errors.length > 0) {
|
|
293
|
+
const error = result.errors[0];
|
|
294
|
+
throw new errors_1.ValidationError(`Row ${index + 1}: ${error.message} (field: ${error.field})`);
|
|
295
|
+
}
|
|
296
|
+
return row;
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
if (error instanceof errors_1.ValidationError) {
|
|
300
|
+
throw error;
|
|
301
|
+
}
|
|
302
|
+
console.error(`Validation error at row ${index}: ${error.message}`);
|
|
303
|
+
if (process.env['NODE_ENV'] === 'development') {
|
|
304
|
+
console.error(error.stack);
|
|
305
|
+
}
|
|
306
|
+
return row;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function applySchemaValidation(data, schema) {
|
|
311
|
+
if (!Array.isArray(data)) {
|
|
312
|
+
throw new errors_1.ValidationError('Data must be an array');
|
|
313
|
+
}
|
|
314
|
+
const validationHook = createValidationHook(schema);
|
|
315
|
+
const errors = [];
|
|
316
|
+
const validatedData = [];
|
|
317
|
+
for (let i = 0; i < data.length; i++) {
|
|
318
|
+
try {
|
|
319
|
+
const validatedRow = validationHook(data[i], i, { operation: 'validate' });
|
|
320
|
+
validatedData.push(validatedRow);
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
if (error instanceof errors_1.ValidationError) {
|
|
324
|
+
errors.push({
|
|
325
|
+
row: i + 1,
|
|
326
|
+
message: error.message,
|
|
327
|
+
data: data[i]
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
validatedData.push(data[i]);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
valid: errors.length === 0,
|
|
337
|
+
errors,
|
|
338
|
+
data: validatedData,
|
|
339
|
+
summary: {
|
|
340
|
+
totalRows: data.length,
|
|
341
|
+
validRows: validatedData.length,
|
|
342
|
+
errorCount: errors.length,
|
|
343
|
+
errorRate: data.length > 0 ? (errors.length / data.length) * 100 : 0
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function createValidationHooks(schema) {
|
|
348
|
+
const { TransformHooks } = require('../core/transform-hooks');
|
|
349
|
+
const hooks = new TransformHooks();
|
|
350
|
+
const validationHook = createValidationHook(schema);
|
|
351
|
+
hooks.perRow(validationHook);
|
|
352
|
+
return hooks;
|
|
353
|
+
}
|
|
354
|
+
function createSchemaValidators(schema) {
|
|
355
|
+
const validators = {};
|
|
356
|
+
const properties = schema.properties || schema;
|
|
357
|
+
const requiredFields = schema.required || [];
|
|
358
|
+
if (!properties || typeof properties !== 'object') {
|
|
359
|
+
return validators;
|
|
360
|
+
}
|
|
361
|
+
for (const [key, definition] of Object.entries(properties)) {
|
|
362
|
+
const validator = {
|
|
363
|
+
type: definition.type,
|
|
364
|
+
required: requiredFields.includes(key)
|
|
365
|
+
};
|
|
366
|
+
if (definition.type === 'string' && definition.format) {
|
|
367
|
+
validator.format = (value) => {
|
|
368
|
+
if (definition.format === 'date-time') {
|
|
369
|
+
if (value instanceof Date) {
|
|
370
|
+
return value.toISOString();
|
|
371
|
+
}
|
|
372
|
+
if (typeof value === 'string') {
|
|
373
|
+
const date = new Date(value);
|
|
374
|
+
if (!isNaN(date.getTime())) {
|
|
375
|
+
return date.toISOString();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (definition.format === 'email') {
|
|
380
|
+
if (typeof value === 'string') {
|
|
381
|
+
return value.toLowerCase().trim();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (definition.format === 'uri') {
|
|
385
|
+
if (typeof value === 'string') {
|
|
386
|
+
return value.trim();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return value;
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
validator.validate = (value) => {
|
|
393
|
+
if (value === null || value === undefined) {
|
|
394
|
+
return !validator.required;
|
|
395
|
+
}
|
|
396
|
+
if (definition.type === 'string' && typeof value !== 'string') {
|
|
397
|
+
if (definition.format === 'date-time' && value instanceof Date) {
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
if (definition.type === 'number' && typeof value !== 'number') {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
if (definition.type === 'integer' && (!Number.isInteger(value) || typeof value !== 'number')) {
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
if (definition.type === 'boolean' && typeof value !== 'boolean') {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
if (definition.type === 'array' && !Array.isArray(value)) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
if (definition.type === 'object' && (typeof value !== 'object' || value === null || Array.isArray(value))) {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
if (definition.type === 'string') {
|
|
418
|
+
if (definition.minLength !== undefined && value.length < definition.minLength) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
if (definition.maxLength !== undefined && value.length > definition.maxLength) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
if (definition.pattern && !new RegExp(definition.pattern).test(value)) {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
if (definition.format === 'email' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
if (definition.format === 'uri') {
|
|
431
|
+
try {
|
|
432
|
+
new URL(value);
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (definition.type === 'number' || definition.type === 'integer') {
|
|
440
|
+
if (definition.minimum !== undefined && value < definition.minimum) {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
if (definition.maximum !== undefined && value > definition.maximum) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
if (definition.exclusiveMinimum !== undefined && value <= definition.exclusiveMinimum) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
if (definition.exclusiveMaximum !== undefined && value >= definition.exclusiveMaximum) {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
if (definition.multipleOf !== undefined && value % definition.multipleOf !== 0) {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (definition.type === 'array') {
|
|
457
|
+
if (definition.minItems !== undefined && value.length < definition.minItems) {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
if (definition.maxItems !== undefined && value.length > definition.maxItems) {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
if (definition.uniqueItems && new Set(value).size !== value.length) {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
if (definition.items) {
|
|
467
|
+
for (const item of value) {
|
|
468
|
+
const itemValidator = createSchemaValidators({ properties: { item: definition.items } });
|
|
469
|
+
if (itemValidator.item && !itemValidator.item.validate(item)) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (definition.type === 'object' && definition.properties) {
|
|
476
|
+
const nestedValidators = createSchemaValidators(definition);
|
|
477
|
+
for (const [nestedKey, nestedValidator] of Object.entries(nestedValidators)) {
|
|
478
|
+
if (value[nestedKey] !== undefined && !nestedValidator.validate(value[nestedKey])) {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
if (nestedValidator.required && value[nestedKey] === undefined) {
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (definition.enum && !definition.enum.includes(value)) {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
return true;
|
|
490
|
+
};
|
|
491
|
+
validators[key] = validator;
|
|
492
|
+
}
|
|
493
|
+
return validators;
|
|
494
|
+
}
|
|
495
|
+
async function loadSchemaAsync(schemaPathOrJson) {
|
|
496
|
+
if (!schemaPathOrJson || typeof schemaPathOrJson !== 'string') {
|
|
497
|
+
throw new errors_1.ValidationError('Schema must be a string (JSON or file path)');
|
|
498
|
+
}
|
|
499
|
+
let schemaString = schemaPathOrJson;
|
|
500
|
+
const isFilePath = schemaPathOrJson.endsWith('.json') ||
|
|
501
|
+
schemaPathOrJson.includes('/') ||
|
|
502
|
+
schemaPathOrJson.includes('\\');
|
|
503
|
+
if (isFilePath) {
|
|
504
|
+
const safePath = path.resolve(schemaPathOrJson);
|
|
505
|
+
const normalizedPath = path.normalize(schemaPathOrJson);
|
|
506
|
+
if (normalizedPath.includes('..') ||
|
|
507
|
+
/\\\.\.\\|\/\.\.\//.test(schemaPathOrJson) ||
|
|
508
|
+
schemaPathOrJson.startsWith('..') ||
|
|
509
|
+
schemaPathOrJson.includes('/..')) {
|
|
510
|
+
throw new errors_1.SecurityError('Directory traversal detected in schema file path');
|
|
511
|
+
}
|
|
512
|
+
try {
|
|
513
|
+
await fsPromises.access(safePath);
|
|
514
|
+
}
|
|
515
|
+
catch {
|
|
516
|
+
throw new errors_1.ValidationError(`Schema file not found: ${schemaPathOrJson}`);
|
|
517
|
+
}
|
|
518
|
+
if (!safePath.toLowerCase().endsWith('.json')) {
|
|
519
|
+
throw new errors_1.ValidationError('Schema file must have .json extension');
|
|
520
|
+
}
|
|
521
|
+
try {
|
|
522
|
+
schemaString = await fsPromises.readFile(safePath, 'utf8');
|
|
523
|
+
}
|
|
524
|
+
catch (error) {
|
|
525
|
+
if (error.code === 'EACCES') {
|
|
526
|
+
throw new errors_1.SecurityError(`Permission denied reading schema file: ${schemaPathOrJson}`);
|
|
527
|
+
}
|
|
528
|
+
throw new errors_1.ValidationError(`Failed to read schema file: ${error.message}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
try {
|
|
532
|
+
const schema = JSON.parse(schemaString);
|
|
533
|
+
if (typeof schema !== 'object' || schema === null) {
|
|
534
|
+
throw new errors_1.ValidationError('Schema must be a JSON object');
|
|
535
|
+
}
|
|
536
|
+
return schema;
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
if (error instanceof SyntaxError) {
|
|
540
|
+
throw new errors_1.ValidationError(`Invalid JSON in schema: ${error.message}`);
|
|
541
|
+
}
|
|
542
|
+
throw new errors_1.ValidationError(`Failed to parse schema: ${error.message}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
async function applySchemaValidationAsync(data, schema) {
|
|
546
|
+
if (!Array.isArray(data)) {
|
|
547
|
+
throw new errors_1.ValidationError('Data must be an array');
|
|
548
|
+
}
|
|
549
|
+
const schemaObj = typeof schema === 'string' ? await loadSchemaAsync(schema) : schema;
|
|
550
|
+
const validationHook = createValidationHook(schemaObj);
|
|
551
|
+
if (data.length > 1000) {
|
|
552
|
+
const { createWorkerPool } = require('../workers/worker-pool');
|
|
553
|
+
const pool = createWorkerPool({
|
|
554
|
+
workerCount: Math.min(4, require('os').cpus().length),
|
|
555
|
+
workerScript: require.resolve('./validation-worker.js')
|
|
556
|
+
});
|
|
557
|
+
try {
|
|
558
|
+
const validationPromises = data.map((row, index) => pool.execute({ row, index, schema: schemaObj, operation: 'validate' }));
|
|
559
|
+
const results = await Promise.all(validationPromises);
|
|
560
|
+
const errors = [];
|
|
561
|
+
const validatedData = [];
|
|
562
|
+
results.forEach((result, index) => {
|
|
563
|
+
if (result.error) {
|
|
564
|
+
errors.push({
|
|
565
|
+
row: index + 1,
|
|
566
|
+
message: result.error.message,
|
|
567
|
+
data: data[index]
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
validatedData.push(result.validatedRow);
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
return {
|
|
575
|
+
valid: errors.length === 0,
|
|
576
|
+
errors,
|
|
577
|
+
data: validatedData,
|
|
578
|
+
summary: {
|
|
579
|
+
totalRows: data.length,
|
|
580
|
+
validRows: validatedData.length,
|
|
581
|
+
errorCount: errors.length,
|
|
582
|
+
errorRate: data.length > 0 ? (errors.length / data.length) * 100 : 0
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
finally {
|
|
587
|
+
await pool.terminate();
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return applySchemaValidation(data, schemaObj);
|
|
591
|
+
}
|
|
592
|
+
function createAsyncValidationHook(schema) {
|
|
593
|
+
const syncHook = createValidationHook(schema);
|
|
594
|
+
return async function (row, index, context) {
|
|
595
|
+
return Promise.resolve(syncHook(row, index, context));
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
exports.default = {
|
|
599
|
+
loadSchema,
|
|
600
|
+
loadSchemaAsync,
|
|
601
|
+
createValidationHook,
|
|
602
|
+
createAsyncValidationHook,
|
|
603
|
+
applySchemaValidation,
|
|
604
|
+
applySchemaValidationAsync,
|
|
605
|
+
createValidationHooks,
|
|
606
|
+
checkType,
|
|
607
|
+
createSchemaValidators
|
|
608
|
+
};
|
|
609
|
+
//# sourceMappingURL=schema-validator.js.map
|