jtcsv 2.1.5 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/bin/jtcsv.js +2462 -1372
- package/examples/error-handling.js +324 -0
- package/examples/ndjson-processing.js +434 -0
- package/examples/react-integration.jsx +637 -0
- package/examples/schema-validation.js +640 -0
- package/examples/typescript-example.ts +486 -0
- package/index.d.ts +2 -0
- package/json-to-csv.js +171 -131
- package/package.json +21 -9
- package/src/errors.js +26 -0
- package/src/utils/schema-validator.js +594 -0
- package/src/utils/transform-loader.js +205 -0
- package/src/web-server/index.js +683 -0
- package/stream-csv-to-json.js +7 -87
- package/stream-json-to-csv.js +7 -87
package/json-to-csv.js
CHANGED
|
@@ -16,6 +16,8 @@ const {
|
|
|
16
16
|
safeExecute
|
|
17
17
|
} = require('./errors');
|
|
18
18
|
|
|
19
|
+
// Add schema validator import
|
|
20
|
+
const { createSchemaValidators } = require('./src/utils/schema-validator');
|
|
19
21
|
/**
|
|
20
22
|
* Validates input data and options
|
|
21
23
|
* @private
|
|
@@ -62,6 +64,11 @@ function validateInput(data, options) {
|
|
|
62
64
|
throw new ConfigurationError('rfc4180Compliant must be a boolean');
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
// Validate schema
|
|
68
|
+
if (options?.schema && typeof options.schema !== 'object') {
|
|
69
|
+
throw new ConfigurationError('schema must be an object');
|
|
70
|
+
}
|
|
71
|
+
|
|
65
72
|
return true;
|
|
66
73
|
}
|
|
67
74
|
|
|
@@ -77,6 +84,7 @@ function validateInput(data, options) {
|
|
|
77
84
|
* @param {number} [options.maxRecords] - Maximum number of records to process (optional, no limit by default)
|
|
78
85
|
* @param {boolean} [options.preventCsvInjection=true] - Prevent CSV injection attacks by escaping formulas
|
|
79
86
|
* @param {boolean} [options.rfc4180Compliant=true] - Ensure RFC 4180 compliance (proper quoting, line endings)
|
|
87
|
+
* @param {Object} [options.schema] - JSON schema for data validation and formatting
|
|
80
88
|
* @returns {string} CSV formatted string
|
|
81
89
|
*
|
|
82
90
|
* @example
|
|
@@ -108,9 +116,16 @@ function jsonToCsv(data, options = {}) {
|
|
|
108
116
|
template = {},
|
|
109
117
|
maxRecords,
|
|
110
118
|
preventCsvInjection = true,
|
|
111
|
-
rfc4180Compliant = true
|
|
119
|
+
rfc4180Compliant = true,
|
|
120
|
+
schema = null
|
|
112
121
|
} = opts;
|
|
113
122
|
|
|
123
|
+
// Initialize schema validators if schema is provided
|
|
124
|
+
let schemaValidators = null;
|
|
125
|
+
if (schema) {
|
|
126
|
+
schemaValidators = createSchemaValidators(schema);
|
|
127
|
+
}
|
|
128
|
+
|
|
114
129
|
// Handle empty data
|
|
115
130
|
if (data.length === 0) {
|
|
116
131
|
return '';
|
|
@@ -135,68 +150,68 @@ function jsonToCsv(data, options = {}) {
|
|
|
135
150
|
);
|
|
136
151
|
}
|
|
137
152
|
|
|
138
|
-
// Get all unique keys from all objects with minimal allocations.
|
|
139
|
-
const allKeys = new Set();
|
|
140
|
-
const originalKeys = [];
|
|
141
|
-
for (let i = 0; i < data.length; i++) {
|
|
142
|
-
const item = data[i];
|
|
143
|
-
if (!item || typeof item !== 'object') {
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
for (const key in item) {
|
|
147
|
-
if (Object.prototype.hasOwnProperty.call(item, key) && !allKeys.has(key)) {
|
|
148
|
-
allKeys.add(key);
|
|
149
|
-
originalKeys.push(key);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const hasRenameMap = Object.keys(renameMap).length > 0;
|
|
155
|
-
const hasTemplate = Object.keys(template).length > 0;
|
|
156
|
-
|
|
157
|
-
// Apply rename map to create header names.
|
|
158
|
-
let headers = originalKeys;
|
|
159
|
-
let reverseRenameMap = null;
|
|
160
|
-
if (hasRenameMap) {
|
|
161
|
-
headers = new Array(originalKeys.length);
|
|
162
|
-
reverseRenameMap = {};
|
|
163
|
-
for (let i = 0; i < originalKeys.length; i++) {
|
|
164
|
-
const key = originalKeys[i];
|
|
165
|
-
const header = renameMap[key] || key;
|
|
166
|
-
headers[i] = header;
|
|
167
|
-
reverseRenameMap[header] = key;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Apply template ordering if provided.
|
|
172
|
-
let finalHeaders = headers;
|
|
173
|
-
if (hasTemplate) {
|
|
174
|
-
const templateKeys = Object.keys(template);
|
|
175
|
-
const templateHeaders = hasRenameMap
|
|
176
|
-
? templateKeys.map(key => renameMap[key] || key)
|
|
177
|
-
: templateKeys;
|
|
178
|
-
const templateHeaderSet = new Set(templateHeaders);
|
|
179
|
-
const extraHeaders = [];
|
|
180
|
-
for (let i = 0; i < headers.length; i++) {
|
|
181
|
-
const header = headers[i];
|
|
182
|
-
if (!templateHeaderSet.has(header)) {
|
|
183
|
-
extraHeaders.push(header);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
finalHeaders = templateHeaders.concat(extraHeaders);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const finalKeys = new Array(finalHeaders.length);
|
|
190
|
-
if (hasRenameMap) {
|
|
191
|
-
for (let i = 0; i < finalHeaders.length; i++) {
|
|
192
|
-
const header = finalHeaders[i];
|
|
193
|
-
finalKeys[i] = reverseRenameMap[header] || header;
|
|
194
|
-
}
|
|
195
|
-
} else {
|
|
196
|
-
for (let i = 0; i < finalHeaders.length; i++) {
|
|
197
|
-
finalKeys[i] = finalHeaders[i];
|
|
198
|
-
}
|
|
199
|
-
}
|
|
153
|
+
// Get all unique keys from all objects with minimal allocations.
|
|
154
|
+
const allKeys = new Set();
|
|
155
|
+
const originalKeys = [];
|
|
156
|
+
for (let i = 0; i < data.length; i++) {
|
|
157
|
+
const item = data[i];
|
|
158
|
+
if (!item || typeof item !== 'object') {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
for (const key in item) {
|
|
162
|
+
if (Object.prototype.hasOwnProperty.call(item, key) && !allKeys.has(key)) {
|
|
163
|
+
allKeys.add(key);
|
|
164
|
+
originalKeys.push(key);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const hasRenameMap = Object.keys(renameMap).length > 0;
|
|
170
|
+
const hasTemplate = Object.keys(template).length > 0;
|
|
171
|
+
|
|
172
|
+
// Apply rename map to create header names.
|
|
173
|
+
let headers = originalKeys;
|
|
174
|
+
let reverseRenameMap = null;
|
|
175
|
+
if (hasRenameMap) {
|
|
176
|
+
headers = new Array(originalKeys.length);
|
|
177
|
+
reverseRenameMap = {};
|
|
178
|
+
for (let i = 0; i < originalKeys.length; i++) {
|
|
179
|
+
const key = originalKeys[i];
|
|
180
|
+
const header = renameMap[key] || key;
|
|
181
|
+
headers[i] = header;
|
|
182
|
+
reverseRenameMap[header] = key;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Apply template ordering if provided.
|
|
187
|
+
let finalHeaders = headers;
|
|
188
|
+
if (hasTemplate) {
|
|
189
|
+
const templateKeys = Object.keys(template);
|
|
190
|
+
const templateHeaders = hasRenameMap
|
|
191
|
+
? templateKeys.map(key => renameMap[key] || key)
|
|
192
|
+
: templateKeys;
|
|
193
|
+
const templateHeaderSet = new Set(templateHeaders);
|
|
194
|
+
const extraHeaders = [];
|
|
195
|
+
for (let i = 0; i < headers.length; i++) {
|
|
196
|
+
const header = headers[i];
|
|
197
|
+
if (!templateHeaderSet.has(header)) {
|
|
198
|
+
extraHeaders.push(header);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
finalHeaders = templateHeaders.concat(extraHeaders);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const finalKeys = new Array(finalHeaders.length);
|
|
205
|
+
if (hasRenameMap) {
|
|
206
|
+
for (let i = 0; i < finalHeaders.length; i++) {
|
|
207
|
+
const header = finalHeaders[i];
|
|
208
|
+
finalKeys[i] = reverseRenameMap[header] || header;
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
for (let i = 0; i < finalHeaders.length; i++) {
|
|
212
|
+
finalKeys[i] = finalHeaders[i];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
200
215
|
|
|
201
216
|
/**
|
|
202
217
|
* Escapes a value for CSV format with CSV injection protection
|
|
@@ -205,75 +220,100 @@ function jsonToCsv(data, options = {}) {
|
|
|
205
220
|
* @param {*} value - The value to escape
|
|
206
221
|
* @returns {string} Escaped CSV value
|
|
207
222
|
*/
|
|
208
|
-
const quoteRegex = /"/g;
|
|
209
|
-
const delimiterCode = delimiter.charCodeAt(0);
|
|
210
|
-
|
|
211
|
-
const escapeValue = (value) => {
|
|
212
|
-
if (value === null || value === undefined || value === '') {
|
|
213
|
-
return '';
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
let stringValue = value;
|
|
217
|
-
if (typeof stringValue !== 'string') {
|
|
218
|
-
stringValue = String(stringValue);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// CSV Injection protection - escape formulas if enabled
|
|
222
|
-
let escapedValue = stringValue;
|
|
223
|
-
if (preventCsvInjection) {
|
|
224
|
-
const firstCharCode = stringValue.charCodeAt(0);
|
|
225
|
-
if (firstCharCode === 61 || firstCharCode === 43 || firstCharCode === 45 || firstCharCode === 64) {
|
|
226
|
-
escapedValue = "'" + stringValue;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
let needsQuoting = false;
|
|
231
|
-
let hasQuote = false;
|
|
232
|
-
for (let i = 0; i < escapedValue.length; i++) {
|
|
233
|
-
const code = escapedValue.charCodeAt(i);
|
|
234
|
-
if (code === 34) {
|
|
235
|
-
hasQuote = true;
|
|
236
|
-
needsQuoting = true;
|
|
237
|
-
} else if (code === delimiterCode || code === 10 || code === 13) {
|
|
238
|
-
needsQuoting = true;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (needsQuoting) {
|
|
243
|
-
const quotedValue = hasQuote ? escapedValue.replace(quoteRegex, '""') : escapedValue;
|
|
244
|
-
return `"${quotedValue}"`;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return escapedValue;
|
|
248
|
-
};
|
|
223
|
+
const quoteRegex = /"/g;
|
|
224
|
+
const delimiterCode = delimiter.charCodeAt(0);
|
|
225
|
+
|
|
226
|
+
const escapeValue = (value) => {
|
|
227
|
+
if (value === null || value === undefined || value === '') {
|
|
228
|
+
return '';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let stringValue = value;
|
|
232
|
+
if (typeof stringValue !== 'string') {
|
|
233
|
+
stringValue = String(stringValue);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// CSV Injection protection - escape formulas if enabled
|
|
237
|
+
let escapedValue = stringValue;
|
|
238
|
+
if (preventCsvInjection) {
|
|
239
|
+
const firstCharCode = stringValue.charCodeAt(0);
|
|
240
|
+
if (firstCharCode === 61 || firstCharCode === 43 || firstCharCode === 45 || firstCharCode === 64) {
|
|
241
|
+
escapedValue = "'" + stringValue;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let needsQuoting = false;
|
|
246
|
+
let hasQuote = false;
|
|
247
|
+
for (let i = 0; i < escapedValue.length; i++) {
|
|
248
|
+
const code = escapedValue.charCodeAt(i);
|
|
249
|
+
if (code === 34) {
|
|
250
|
+
hasQuote = true;
|
|
251
|
+
needsQuoting = true;
|
|
252
|
+
} else if (code === delimiterCode || code === 10 || code === 13) {
|
|
253
|
+
needsQuoting = true;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (needsQuoting) {
|
|
258
|
+
const quotedValue = hasQuote ? escapedValue.replace(quoteRegex, '""') : escapedValue;
|
|
259
|
+
return `"${quotedValue}"`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return escapedValue;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// Build CSV rows.
|
|
266
|
+
const rows = [];
|
|
267
|
+
const columnCount = finalHeaders.length;
|
|
268
|
+
|
|
269
|
+
// Add headers row if requested.
|
|
270
|
+
if (includeHeaders && columnCount > 0) {
|
|
271
|
+
rows.push(finalHeaders.join(delimiter));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Add data rows.
|
|
275
|
+
const rowValues = new Array(columnCount);
|
|
276
|
+
for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
|
|
277
|
+
const item = data[rowIndex];
|
|
278
|
+
if (!item || typeof item !== 'object') {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Apply schema validation and formatting if schema is provided
|
|
283
|
+
let processedItem = item;
|
|
284
|
+
if (schemaValidators) {
|
|
285
|
+
processedItem = { ...item };
|
|
286
|
+
for (const [key, validator] of Object.entries(schemaValidators)) {
|
|
287
|
+
if (key in processedItem) {
|
|
288
|
+
const value = processedItem[key];
|
|
289
|
+
// Validate value
|
|
290
|
+
if (!validator.validate(value)) {
|
|
291
|
+
throw new ValidationError(
|
|
292
|
+
`Row ${rowIndex + 1}: Value for field "${key}" does not match schema`
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
// Format value if formatter exists
|
|
296
|
+
if (validator.format) {
|
|
297
|
+
processedItem[key] = validator.format(value);
|
|
298
|
+
}
|
|
299
|
+
} else if (validator.required) {
|
|
300
|
+
throw new ValidationError(
|
|
301
|
+
`Row ${rowIndex + 1}: Required field "${key}" is missing`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
for (let i = 0; i < columnCount; i++) {
|
|
308
|
+
rowValues[i] = escapeValue(processedItem[finalKeys[i]]);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
rows.push(rowValues.join(delimiter));
|
|
312
|
+
}
|
|
249
313
|
|
|
250
|
-
//
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
// Add headers row if requested.
|
|
255
|
-
if (includeHeaders && columnCount > 0) {
|
|
256
|
-
rows.push(finalHeaders.join(delimiter));
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Add data rows.
|
|
260
|
-
const rowValues = new Array(columnCount);
|
|
261
|
-
for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
|
|
262
|
-
const item = data[rowIndex];
|
|
263
|
-
if (!item || typeof item !== 'object') {
|
|
264
|
-
continue;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
for (let i = 0; i < columnCount; i++) {
|
|
268
|
-
rowValues[i] = escapeValue(item[finalKeys[i]]);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
rows.push(rowValues.join(delimiter));
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// RFC 4180: Each record is located on a separate line, delimited by a line break (CRLF).
|
|
275
|
-
const lineEnding = rfc4180Compliant ? '\r\n' : '\n';
|
|
276
|
-
return rows.join(lineEnding);
|
|
314
|
+
// RFC 4180: Each record is located on a separate line, delimited by a line break (CRLF).
|
|
315
|
+
const lineEnding = rfc4180Compliant ? '\r\n' : '\n';
|
|
316
|
+
return rows.join(lineEnding);
|
|
277
317
|
}, 'PARSE_FAILED', { function: 'jsonToCsv' });
|
|
278
318
|
}
|
|
279
319
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jtcsv",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.2",
|
|
4
4
|
"description": "Complete JSON<->CSV and CSV<->JSON converter for Node.js and Browser with streaming, security, Web Workers, TypeScript support, and optional ecosystem (zero-deps core)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"browser": "dist/jtcsv.umd.js",
|
|
@@ -86,7 +86,12 @@
|
|
|
86
86
|
"test:plugins": "jest __tests__/plugin-system.test.js",
|
|
87
87
|
"test:fastpath": "jest __tests__/fast-path-engine.test.js",
|
|
88
88
|
"test:ndjson": "jest __tests__/ndjson-parser.test.js",
|
|
89
|
-
"test:performance": "jest __tests__/*.test.js --testNamePattern=\"
|
|
89
|
+
"test:performance": "jest __tests__/*.test.js --testNamePattern=\"Производительность|производительность\"",
|
|
90
|
+
"test:benchmark": "jest __tests__/benchmark-suite.test.js --testTimeout=60000",
|
|
91
|
+
"test:load": "jest __tests__/load-tests.test.js --testTimeout=300000",
|
|
92
|
+
"test:load:large": "LOAD_TEST_SIZE=large jest __tests__/load-tests.test.js --testTimeout=300000",
|
|
93
|
+
"test:security": "jest __tests__/security-fuzzing.test.js --testTimeout=60000",
|
|
94
|
+
"test:memory": "node --expose-gc node_modules/jest/bin/jest __tests__/memory-profiling.test.js --testTimeout=120000",
|
|
90
95
|
"test:express": "cd plugins/express-middleware && npm test",
|
|
91
96
|
"test:fastify": "cd plugins/fastify-plugin && npm test",
|
|
92
97
|
"test:nextjs": "cd plugins/nextjs-api && npm test",
|
|
@@ -113,7 +118,10 @@
|
|
|
113
118
|
"profile:perf": "node scripts/profile-performance.js",
|
|
114
119
|
"example:plugins": "node examples/plugin-excel-exporter.js",
|
|
115
120
|
"example:express": "cd plugins/express-middleware && node example.js",
|
|
116
|
-
"plugins:build": "npm run build && cd plugins/express-middleware && npm run build && cd ../fastify-plugin && npm run build && cd ../nextjs-api && npm run build"
|
|
121
|
+
"plugins:build": "npm run build && cd plugins/express-middleware && npm run build && cd ../fastify-plugin && npm run build && cd ../nextjs-api && npm run build",
|
|
122
|
+
"docs": "typedoc",
|
|
123
|
+
"docs:watch": "typedoc --watch",
|
|
124
|
+
"docs:serve": "typedoc && npx serve docs/api"
|
|
117
125
|
},
|
|
118
126
|
"keywords": [
|
|
119
127
|
"json",
|
|
@@ -175,7 +183,7 @@
|
|
|
175
183
|
},
|
|
176
184
|
"homepage": "https://github.com/Linol-Hamelton/jtcsv#readme",
|
|
177
185
|
"engines": {
|
|
178
|
-
"node": ">=
|
|
186
|
+
"node": ">=18.0.0"
|
|
179
187
|
},
|
|
180
188
|
"files": [
|
|
181
189
|
"index.js",
|
|
@@ -192,6 +200,9 @@
|
|
|
192
200
|
"examples/",
|
|
193
201
|
"plugins/"
|
|
194
202
|
],
|
|
203
|
+
"dependencies": {
|
|
204
|
+
"glob": "10.5.0"
|
|
205
|
+
},
|
|
195
206
|
"devDependencies": {
|
|
196
207
|
"@babel/core": "^7.23.0",
|
|
197
208
|
"@babel/preset-env": "^7.22.0",
|
|
@@ -199,14 +210,15 @@
|
|
|
199
210
|
"@rollup/plugin-commonjs": "^25.0.0",
|
|
200
211
|
"@rollup/plugin-node-resolve": "^15.0.0",
|
|
201
212
|
"@rollup/plugin-terser": "^0.4.0",
|
|
202
|
-
"@size-limit/preset-small-lib": "12.0.0",
|
|
203
|
-
"blessed": "^0.1.81",
|
|
204
|
-
"blessed-contrib": "4.11.0",
|
|
205
|
-
"eslint": "8.57.1",
|
|
213
|
+
"@size-limit/preset-small-lib": "12.0.0",
|
|
214
|
+
"blessed": "^0.1.81",
|
|
215
|
+
"blessed-contrib": "4.11.0",
|
|
216
|
+
"eslint": "8.57.1",
|
|
206
217
|
"jest": "^29.0.0",
|
|
207
218
|
"jest-environment-jsdom": "30.2.0",
|
|
208
219
|
"rollup": "^4.0.0",
|
|
209
|
-
"size-limit": "12.0.0"
|
|
220
|
+
"size-limit": "12.0.0",
|
|
221
|
+
"typedoc": "^0.25.0"
|
|
210
222
|
},
|
|
211
223
|
"type": "commonjs",
|
|
212
224
|
"size-limit": [
|
package/src/errors.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class ValidationError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'ValidationError';
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
class SecurityError extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'SecurityError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class ConfigurationError extends Error {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'ConfigurationError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
ValidationError,
|
|
24
|
+
SecurityError,
|
|
25
|
+
ConfigurationError
|
|
26
|
+
};
|