configforge 1.0.0-beta.1 → 1.0.0-beta.10
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 +574 -11
- package/dist/core/ConversionResult.d.ts +22 -0
- package/dist/core/ConversionResult.d.ts.map +1 -1
- package/dist/core/ConversionResult.js +44 -30
- package/dist/core/ConversionResult.js.map +1 -1
- package/dist/core/Converter.d.ts +40 -10
- package/dist/core/Converter.d.ts.map +1 -1
- package/dist/core/Converter.js +137 -29
- package/dist/core/Converter.js.map +1 -1
- package/dist/core/Mapper.d.ts +12 -0
- package/dist/core/Mapper.d.ts.map +1 -1
- package/dist/core/Mapper.js +155 -3
- package/dist/core/Mapper.js.map +1 -1
- package/dist/core/Pipeline.d.ts +42 -0
- package/dist/core/Pipeline.d.ts.map +1 -0
- package/dist/core/Pipeline.js +134 -0
- package/dist/core/Pipeline.js.map +1 -0
- package/dist/core/pipeline/PipelineBuilder.d.ts +67 -0
- package/dist/core/pipeline/PipelineBuilder.d.ts.map +1 -0
- package/dist/core/pipeline/PipelineBuilder.js +109 -0
- package/dist/core/pipeline/PipelineBuilder.js.map +1 -0
- package/dist/core/pipeline/PipelineSteps.d.ts +74 -0
- package/dist/core/pipeline/PipelineSteps.d.ts.map +1 -0
- package/dist/core/pipeline/PipelineSteps.js +298 -0
- package/dist/core/pipeline/PipelineSteps.js.map +1 -0
- package/dist/errors/ErrorCollector.d.ts +119 -0
- package/dist/errors/ErrorCollector.d.ts.map +1 -0
- package/dist/errors/ErrorCollector.js +197 -0
- package/dist/errors/ErrorCollector.js.map +1 -0
- package/dist/errors/ErrorContext.d.ts +168 -0
- package/dist/errors/ErrorContext.d.ts.map +1 -0
- package/dist/errors/ErrorContext.js +356 -0
- package/dist/errors/ErrorContext.js.map +1 -0
- package/dist/errors/ErrorRecovery.d.ts +91 -0
- package/dist/errors/ErrorRecovery.d.ts.map +1 -0
- package/dist/errors/ErrorRecovery.js +321 -0
- package/dist/errors/ErrorRecovery.js.map +1 -0
- package/dist/errors/ErrorReporter.d.ts +58 -0
- package/dist/errors/ErrorReporter.d.ts.map +1 -0
- package/dist/errors/ErrorReporter.js +262 -0
- package/dist/errors/ErrorReporter.js.map +1 -0
- package/dist/errors/ErrorTypes.d.ts +103 -0
- package/dist/errors/ErrorTypes.d.ts.map +1 -0
- package/dist/errors/ErrorTypes.js +125 -0
- package/dist/errors/ErrorTypes.js.map +1 -0
- package/dist/errors/SpecificErrors.d.ts +45 -0
- package/dist/errors/SpecificErrors.d.ts.map +1 -0
- package/dist/errors/SpecificErrors.js +124 -0
- package/dist/errors/SpecificErrors.js.map +1 -0
- package/dist/errors/SuggestionEngine.d.ts +63 -0
- package/dist/errors/SuggestionEngine.d.ts.map +1 -0
- package/dist/errors/SuggestionEngine.js +328 -0
- package/dist/errors/SuggestionEngine.js.map +1 -0
- package/dist/errors/index.d.ts +12 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +39 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +8 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/validators/ValidatorRegistry.d.ts +55 -0
- package/dist/validators/ValidatorRegistry.d.ts.map +1 -0
- package/dist/validators/ValidatorRegistry.js +140 -0
- package/dist/validators/ValidatorRegistry.js.map +1 -0
- package/dist/validators/built-in.d.ts +93 -0
- package/dist/validators/built-in.d.ts.map +1 -0
- package/dist/validators/built-in.js +290 -0
- package/dist/validators/built-in.js.map +1 -0
- package/dist/validators/index.d.ts +4 -0
- package/dist/validators/index.d.ts.map +1 -0
- package/dist/validators/index.js +24 -0
- package/dist/validators/index.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# ConfigForge
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> A TypeScript library for converting, mapping, and transforming config data in JSON and YAML.
|
|
4
|
+
|
|
5
|
+
ConfigForge is a universal config converter library with a fluent API
|
|
4
6
|
|
|
5
7
|
ConfigForge makes it easy to convert configuration files between different formats and structures. Just define your mappings and let ConfigForge handle the rest.
|
|
6
8
|
|
|
@@ -86,17 +88,123 @@ const converter = forge()
|
|
|
86
88
|
})
|
|
87
89
|
```
|
|
88
90
|
|
|
89
|
-
### 4.
|
|
91
|
+
### 4. Add Conditional Logic
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
// Function conditions - only apply when condition returns true
|
|
95
|
+
.when(source => source.user?.type === 'admin')
|
|
96
|
+
.map('user.permissions', 'adminPermissions')
|
|
97
|
+
.map('user.role', 'adminRole')
|
|
98
|
+
.end()
|
|
99
|
+
|
|
100
|
+
// String conditions - only apply when field exists and is truthy
|
|
101
|
+
.when('user.isActive')
|
|
102
|
+
.map('user.lastLogin', 'lastActiveLogin')
|
|
103
|
+
.end()
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 5. Merge Multiple Fields
|
|
90
107
|
|
|
91
108
|
```javascript
|
|
109
|
+
// Combine multiple source fields into one target field
|
|
110
|
+
.merge(['firstName', 'lastName'], 'fullName', (first, last) => `${first} ${last}`)
|
|
111
|
+
|
|
112
|
+
// Merge with transformation
|
|
113
|
+
.merge(['basePrice', 'tax'], 'totalPrice',
|
|
114
|
+
(price, tax) => price + tax,
|
|
115
|
+
total => `$${total.toFixed(2)}`
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
// Merge complex data
|
|
119
|
+
.merge(['user.profile', 'user.settings'], 'userInfo', (profile, settings) => ({
|
|
120
|
+
...profile,
|
|
121
|
+
...settings
|
|
122
|
+
}))
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 6. Set Default Values
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
// Set static default values
|
|
92
129
|
.defaults({
|
|
93
130
|
version: '1.0.0',
|
|
94
131
|
enabled: true,
|
|
95
132
|
environment: 'production'
|
|
96
133
|
})
|
|
134
|
+
|
|
135
|
+
// Set dynamic default values using functions
|
|
136
|
+
.defaults({
|
|
137
|
+
timestamp: () => new Date().toISOString(),
|
|
138
|
+
id: () => `user_${Date.now()}`,
|
|
139
|
+
sessionId: () => Math.random().toString(36).substr(2, 9)
|
|
140
|
+
})
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 7. Add Lifecycle Hooks
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
// Before hooks - run before conversion starts
|
|
147
|
+
.before((sourceData) => {
|
|
148
|
+
console.log('Starting conversion...');
|
|
149
|
+
// Modify source data if needed
|
|
150
|
+
return {
|
|
151
|
+
...sourceData,
|
|
152
|
+
processed: true
|
|
153
|
+
};
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
// After hooks - run after conversion completes
|
|
157
|
+
.after((targetData) => {
|
|
158
|
+
console.log('Conversion completed!');
|
|
159
|
+
// Add computed fields or modify target data
|
|
160
|
+
return {
|
|
161
|
+
...targetData,
|
|
162
|
+
processedAt: new Date().toISOString(),
|
|
163
|
+
checksum: calculateChecksum(targetData)
|
|
164
|
+
};
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
// Multiple hooks execute in order
|
|
168
|
+
.before(validateInput)
|
|
169
|
+
.before(preprocessData)
|
|
170
|
+
.after(addMetadata)
|
|
171
|
+
.after(logResults)
|
|
172
|
+
|
|
173
|
+
// Async hooks are supported
|
|
174
|
+
.before(async (data) => {
|
|
175
|
+
const enrichedData = await fetchAdditionalData(data.userId);
|
|
176
|
+
return { ...data, ...enrichedData };
|
|
177
|
+
})
|
|
97
178
|
```
|
|
98
179
|
|
|
99
|
-
###
|
|
180
|
+
### 8. Add Validation
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
const { validators } = require('configforge');
|
|
184
|
+
|
|
185
|
+
// Add field validation
|
|
186
|
+
.validate('email', validators.email)
|
|
187
|
+
.validate('age', validators.all(
|
|
188
|
+
validators.required,
|
|
189
|
+
validators.type('number'),
|
|
190
|
+
validators.range(18, 120)
|
|
191
|
+
))
|
|
192
|
+
.validate('username', validators.all(
|
|
193
|
+
validators.required,
|
|
194
|
+
validators.minLength(3),
|
|
195
|
+
validators.pattern(/^[a-zA-Z0-9_]+$/, 'Only letters, numbers, and underscores allowed')
|
|
196
|
+
))
|
|
197
|
+
|
|
198
|
+
// Custom validation with context
|
|
199
|
+
.validate('password', (value, context) => {
|
|
200
|
+
if (context.source.confirmPassword !== value) {
|
|
201
|
+
return 'Passwords do not match';
|
|
202
|
+
}
|
|
203
|
+
return validators.minLength(8)(value, context);
|
|
204
|
+
})
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### 9. Convert Data
|
|
100
208
|
|
|
101
209
|
```javascript
|
|
102
210
|
// Convert object data
|
|
@@ -227,7 +335,435 @@ console.log(result.data);
|
|
|
227
335
|
// }
|
|
228
336
|
```
|
|
229
337
|
|
|
230
|
-
### Example 3:
|
|
338
|
+
### Example 3: Array Processing with forEach()
|
|
339
|
+
|
|
340
|
+
```javascript
|
|
341
|
+
// Simple fruit inventory
|
|
342
|
+
const fruitData = {
|
|
343
|
+
storeId: 'STORE-001',
|
|
344
|
+
fruits: [
|
|
345
|
+
{
|
|
346
|
+
name: 'Apple',
|
|
347
|
+
color: 'red',
|
|
348
|
+
price: 1.5,
|
|
349
|
+
quantity: 100,
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
name: 'Banana',
|
|
353
|
+
color: 'yellow',
|
|
354
|
+
price: 0.75,
|
|
355
|
+
quantity: 80,
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: 'Orange',
|
|
359
|
+
color: 'orange',
|
|
360
|
+
price: 1.25,
|
|
361
|
+
quantity: 60,
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const converter = forge()
|
|
367
|
+
.from('inventory')
|
|
368
|
+
.to('catalog')
|
|
369
|
+
.map('storeId', 'storeId')
|
|
370
|
+
.forEach('fruits', (fruit, index) => {
|
|
371
|
+
return {
|
|
372
|
+
id: index + 1,
|
|
373
|
+
fruitName: fruit.name,
|
|
374
|
+
displayColor: fruit.color,
|
|
375
|
+
pricePerItem: fruit.price,
|
|
376
|
+
inStock: fruit.quantity,
|
|
377
|
+
totalValue: fruit.price * fruit.quantity,
|
|
378
|
+
isExpensive: fruit.price > 1.0,
|
|
379
|
+
};
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const result = await converter.convert(fruitData);
|
|
383
|
+
console.log(result.data);
|
|
384
|
+
// {
|
|
385
|
+
// storeId: 'STORE-001',
|
|
386
|
+
// fruits: [
|
|
387
|
+
// {
|
|
388
|
+
// id: 1,
|
|
389
|
+
// fruitName: 'Apple',
|
|
390
|
+
// displayColor: 'red',
|
|
391
|
+
// pricePerItem: 1.50,
|
|
392
|
+
// inStock: 100,
|
|
393
|
+
// totalValue: 150,
|
|
394
|
+
// isExpensive: true
|
|
395
|
+
// },
|
|
396
|
+
// {
|
|
397
|
+
// id: 2,
|
|
398
|
+
// fruitName: 'Banana',
|
|
399
|
+
// displayColor: 'yellow',
|
|
400
|
+
// pricePerItem: 0.75,
|
|
401
|
+
// inStock: 80,
|
|
402
|
+
// totalValue: 60,
|
|
403
|
+
// isExpensive: false
|
|
404
|
+
// },
|
|
405
|
+
// // ... more fruits
|
|
406
|
+
// ]
|
|
407
|
+
// }
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Example 4: Conditional Mapping with when()
|
|
411
|
+
|
|
412
|
+
```javascript
|
|
413
|
+
// Different types of pets need different mappings
|
|
414
|
+
const petData = {
|
|
415
|
+
type: 'dog',
|
|
416
|
+
name: 'Buddy',
|
|
417
|
+
age: 3,
|
|
418
|
+
dogInfo: {
|
|
419
|
+
breed: 'Golden Retriever',
|
|
420
|
+
tricks: ['sit', 'stay', 'fetch'],
|
|
421
|
+
},
|
|
422
|
+
catInfo: {
|
|
423
|
+
breed: 'Persian',
|
|
424
|
+
indoor: true,
|
|
425
|
+
},
|
|
426
|
+
birdInfo: {
|
|
427
|
+
species: 'Parrot',
|
|
428
|
+
canTalk: true,
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const converter = forge()
|
|
433
|
+
.from('petData')
|
|
434
|
+
.to('petProfile')
|
|
435
|
+
.map('name', 'petName')
|
|
436
|
+
.map('age', 'petAge')
|
|
437
|
+
.map('type', 'animalType')
|
|
438
|
+
|
|
439
|
+
// Only map dog-specific info for dogs
|
|
440
|
+
.when(source => source.type === 'dog')
|
|
441
|
+
.map('dogInfo.breed', 'breed')
|
|
442
|
+
.map('dogInfo.tricks', 'knownTricks')
|
|
443
|
+
.end()
|
|
444
|
+
|
|
445
|
+
// Only map cat-specific info for cats
|
|
446
|
+
.when(source => source.type === 'cat')
|
|
447
|
+
.map('catInfo.breed', 'breed')
|
|
448
|
+
.map('catInfo.indoor', 'isIndoorCat')
|
|
449
|
+
.end()
|
|
450
|
+
|
|
451
|
+
// Only map bird-specific info for birds
|
|
452
|
+
.when(source => source.type === 'bird')
|
|
453
|
+
.map('birdInfo.species', 'species')
|
|
454
|
+
.map('birdInfo.canTalk', 'canSpeak')
|
|
455
|
+
.end();
|
|
456
|
+
|
|
457
|
+
const result = await converter.convert(petData);
|
|
458
|
+
console.log(result.data);
|
|
459
|
+
// {
|
|
460
|
+
// petName: 'Buddy',
|
|
461
|
+
// petAge: 3,
|
|
462
|
+
// animalType: 'dog',
|
|
463
|
+
// breed: 'Golden Retriever',
|
|
464
|
+
// knownTricks: ['sit', 'stay', 'fetch']
|
|
465
|
+
// }
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Example 5: Merge Multiple Fields with merge()
|
|
469
|
+
|
|
470
|
+
```javascript
|
|
471
|
+
// Simple student report card
|
|
472
|
+
const studentData = {
|
|
473
|
+
student: {
|
|
474
|
+
firstName: 'Alice',
|
|
475
|
+
lastName: 'Smith',
|
|
476
|
+
},
|
|
477
|
+
grades: {
|
|
478
|
+
math: 85,
|
|
479
|
+
english: 92,
|
|
480
|
+
science: 78,
|
|
481
|
+
},
|
|
482
|
+
activities: ['soccer', 'chess', 'art'],
|
|
483
|
+
info: {
|
|
484
|
+
grade: '5th',
|
|
485
|
+
teacher: 'Ms. Johnson',
|
|
486
|
+
},
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
const converter = forge()
|
|
490
|
+
.from('report')
|
|
491
|
+
.to('summary')
|
|
492
|
+
|
|
493
|
+
// Merge student name fields
|
|
494
|
+
.merge(
|
|
495
|
+
['student.firstName', 'student.lastName'],
|
|
496
|
+
'studentName',
|
|
497
|
+
(first, last) => `${first} ${last}`
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
// Calculate total with transformation
|
|
501
|
+
.merge(
|
|
502
|
+
['order.basePrice', 'order.tax', 'order.shipping'],
|
|
503
|
+
'subtotal',
|
|
504
|
+
(base, tax, shipping) => base + tax + shipping
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
.merge(
|
|
508
|
+
['order.basePrice', 'order.tax', 'order.shipping', 'order.discount'],
|
|
509
|
+
'total',
|
|
510
|
+
(base, tax, shipping, discount) => base + tax + shipping - discount,
|
|
511
|
+
total => `$${total.toFixed(2)}`
|
|
512
|
+
) // Transform to currency format
|
|
513
|
+
|
|
514
|
+
// Merge arrays and objects
|
|
515
|
+
.merge(['items', 'metadata'], 'orderSummary', (items, meta) => ({
|
|
516
|
+
itemCount: items.length,
|
|
517
|
+
items: items.join(', '),
|
|
518
|
+
...meta,
|
|
519
|
+
}))
|
|
520
|
+
|
|
521
|
+
// Simple field mappings
|
|
522
|
+
.map('customer.email', 'billingEmail');
|
|
523
|
+
|
|
524
|
+
const result = await converter.convert(orderData);
|
|
525
|
+
console.log(result.data);
|
|
526
|
+
// {
|
|
527
|
+
// customerName: 'John Doe',
|
|
528
|
+
// subtotal: 121.49,
|
|
529
|
+
// total: '$106.49',
|
|
530
|
+
// orderSummary: {
|
|
531
|
+
// itemCount: 3,
|
|
532
|
+
// items: 'laptop, mouse, keyboard',
|
|
533
|
+
// orderDate: '2023-12-01',
|
|
534
|
+
// source: 'web'
|
|
535
|
+
// },
|
|
536
|
+
// billingEmail: 'john.doe@example.com'
|
|
537
|
+
// }
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### Example 6: Data Validation
|
|
541
|
+
|
|
542
|
+
```javascript
|
|
543
|
+
const { forge, validators } = require('configforge');
|
|
544
|
+
|
|
545
|
+
// User registration form data
|
|
546
|
+
const formData = {
|
|
547
|
+
personalInfo: {
|
|
548
|
+
firstName: 'John',
|
|
549
|
+
lastName: 'Doe',
|
|
550
|
+
email: 'john.doe@example.com',
|
|
551
|
+
age: 28,
|
|
552
|
+
},
|
|
553
|
+
accountInfo: {
|
|
554
|
+
username: 'johndoe123',
|
|
555
|
+
password: 'SecurePass123!',
|
|
556
|
+
confirmPassword: 'SecurePass123!',
|
|
557
|
+
},
|
|
558
|
+
preferences: {
|
|
559
|
+
newsletter: 'yes',
|
|
560
|
+
theme: 'dark',
|
|
561
|
+
language: 'en',
|
|
562
|
+
},
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
const converter = forge()
|
|
566
|
+
.from('form')
|
|
567
|
+
.to('user')
|
|
568
|
+
// Map fields
|
|
569
|
+
.merge(
|
|
570
|
+
['personalInfo.firstName', 'personalInfo.lastName'],
|
|
571
|
+
'fullName',
|
|
572
|
+
(first, last) => `${first} ${last}`
|
|
573
|
+
)
|
|
574
|
+
.map('personalInfo.email', 'email')
|
|
575
|
+
.map('personalInfo.age', 'age')
|
|
576
|
+
.map('accountInfo.username', 'username')
|
|
577
|
+
.map('accountInfo.password', 'password')
|
|
578
|
+
.map(
|
|
579
|
+
'preferences.newsletter',
|
|
580
|
+
'subscribeToNewsletter',
|
|
581
|
+
value => value === 'yes'
|
|
582
|
+
)
|
|
583
|
+
.map('preferences.theme', 'theme')
|
|
584
|
+
.map('preferences.language', 'language')
|
|
585
|
+
|
|
586
|
+
// Add validation rules
|
|
587
|
+
.validate(
|
|
588
|
+
'fullName',
|
|
589
|
+
validators.all(
|
|
590
|
+
validators.required,
|
|
591
|
+
validators.minLength(2),
|
|
592
|
+
validators.maxLength(100)
|
|
593
|
+
)
|
|
594
|
+
)
|
|
595
|
+
.validate('email', validators.all(validators.required, validators.email))
|
|
596
|
+
.validate(
|
|
597
|
+
'age',
|
|
598
|
+
validators.all(
|
|
599
|
+
validators.required,
|
|
600
|
+
validators.type('number'),
|
|
601
|
+
validators.range(13, 120)
|
|
602
|
+
)
|
|
603
|
+
)
|
|
604
|
+
.validate(
|
|
605
|
+
'username',
|
|
606
|
+
validators.all(
|
|
607
|
+
validators.required,
|
|
608
|
+
validators.minLength(3),
|
|
609
|
+
validators.maxLength(20),
|
|
610
|
+
validators.pattern(
|
|
611
|
+
/^[a-zA-Z0-9_]+$/,
|
|
612
|
+
'Username can only contain letters, numbers, and underscores'
|
|
613
|
+
)
|
|
614
|
+
)
|
|
615
|
+
)
|
|
616
|
+
.validate(
|
|
617
|
+
'password',
|
|
618
|
+
validators.all(
|
|
619
|
+
validators.required,
|
|
620
|
+
validators.minLength(8),
|
|
621
|
+
validators.pattern(
|
|
622
|
+
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
|
|
623
|
+
'Password must contain lowercase, uppercase, and number'
|
|
624
|
+
)
|
|
625
|
+
)
|
|
626
|
+
)
|
|
627
|
+
.validate('theme', validators.oneOf(['light', 'dark', 'auto']))
|
|
628
|
+
.validate('language', validators.oneOf(['en', 'es', 'fr', 'de']))
|
|
629
|
+
|
|
630
|
+
// Custom validation
|
|
631
|
+
.validate('password', (value, context) => {
|
|
632
|
+
if (context.source.accountInfo.confirmPassword !== value) {
|
|
633
|
+
return 'Passwords do not match';
|
|
634
|
+
}
|
|
635
|
+
return true;
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
const result = await converter.convert(formData);
|
|
639
|
+
|
|
640
|
+
if (result.errors.length > 0) {
|
|
641
|
+
console.log('Validation errors:');
|
|
642
|
+
result.errors.forEach(error => {
|
|
643
|
+
console.log(`- ${error.path}: ${error.message}`);
|
|
644
|
+
});
|
|
645
|
+
} else {
|
|
646
|
+
console.log('User data is valid!');
|
|
647
|
+
console.log(result.data);
|
|
648
|
+
// {
|
|
649
|
+
// fullName: 'John Doe',
|
|
650
|
+
// email: 'john.doe@example.com',
|
|
651
|
+
// age: 28,
|
|
652
|
+
// username: 'johndoe123',
|
|
653
|
+
// password: 'SecurePass123!',
|
|
654
|
+
// subscribeToNewsletter: true,
|
|
655
|
+
// theme: 'dark',
|
|
656
|
+
// language: 'en'
|
|
657
|
+
// }
|
|
658
|
+
}
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### Example 7: Defaults and Hooks
|
|
662
|
+
|
|
663
|
+
```javascript
|
|
664
|
+
// Student data with missing fields
|
|
665
|
+
const studentData = {
|
|
666
|
+
student: {
|
|
667
|
+
firstName: 'Alice',
|
|
668
|
+
lastName: 'Smith',
|
|
669
|
+
// age is missing
|
|
670
|
+
},
|
|
671
|
+
grades: {
|
|
672
|
+
math: 85,
|
|
673
|
+
english: 92,
|
|
674
|
+
// science grade is missing
|
|
675
|
+
},
|
|
676
|
+
// activities array is missing
|
|
677
|
+
info: {
|
|
678
|
+
grade: '5th',
|
|
679
|
+
teacher: 'Ms. Johnson',
|
|
680
|
+
// school year is missing
|
|
681
|
+
},
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
const converter = forge()
|
|
685
|
+
.from('studentReport')
|
|
686
|
+
.to('completeProfile')
|
|
687
|
+
|
|
688
|
+
// Basic mappings
|
|
689
|
+
.map('student.firstName', 'firstName')
|
|
690
|
+
.map('student.lastName', 'lastName')
|
|
691
|
+
.map('student.age', 'age')
|
|
692
|
+
.map('grades.math', 'mathGrade')
|
|
693
|
+
.map('grades.english', 'englishGrade')
|
|
694
|
+
.map('grades.science', 'scienceGrade')
|
|
695
|
+
.map('activities', 'extracurriculars')
|
|
696
|
+
.map('info.grade', 'gradeLevel')
|
|
697
|
+
.map('info.teacher', 'teacher')
|
|
698
|
+
.map('info.schoolYear', 'academicYear')
|
|
699
|
+
|
|
700
|
+
// Set default values for missing fields
|
|
701
|
+
.defaults({
|
|
702
|
+
age: 10, // Default age for 5th graders
|
|
703
|
+
scienceGrade: 80, // Default science grade
|
|
704
|
+
extracurriculars: ['reading'], // Default activity
|
|
705
|
+
academicYear: () => {
|
|
706
|
+
// Dynamic default - current school year
|
|
707
|
+
const now = new Date();
|
|
708
|
+
const year = now.getFullYear();
|
|
709
|
+
const month = now.getMonth();
|
|
710
|
+
return month >= 8 ? `${year}-${year + 1}` : `${year - 1}-${year}`;
|
|
711
|
+
},
|
|
712
|
+
status: 'active',
|
|
713
|
+
lastUpdated: () => new Date().toISOString(),
|
|
714
|
+
})
|
|
715
|
+
|
|
716
|
+
// Add before hook to log conversion start
|
|
717
|
+
.before(data => {
|
|
718
|
+
console.log('🔄 Starting conversion...');
|
|
719
|
+
console.log(
|
|
720
|
+
`Processing student: ${data.student?.firstName} ${data.student?.lastName}`
|
|
721
|
+
);
|
|
722
|
+
return data; // Return the data unchanged
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
// Add after hook to calculate grade average
|
|
726
|
+
.after(data => {
|
|
727
|
+
console.log('✅ Conversion completed!');
|
|
728
|
+
|
|
729
|
+
// Create full name from first and last name
|
|
730
|
+
if (data.firstName && data.lastName) {
|
|
731
|
+
data.fullName = `${data.firstName} ${data.lastName}`;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Calculate and add grade average
|
|
735
|
+
const { mathGrade, englishGrade, scienceGrade } = data;
|
|
736
|
+
if (mathGrade && englishGrade && scienceGrade) {
|
|
737
|
+
data.gradeAverage = Math.round(
|
|
738
|
+
(mathGrade + englishGrade + scienceGrade) / 3
|
|
739
|
+
);
|
|
740
|
+
console.log(`Calculated grade average: ${data.gradeAverage}`);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
return data; // Return the modified data
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
const result = await converter.convert(studentData);
|
|
747
|
+
console.log(result.data);
|
|
748
|
+
// {
|
|
749
|
+
// firstName: 'Alice',
|
|
750
|
+
// lastName: 'Smith',
|
|
751
|
+
// mathGrade: 85,
|
|
752
|
+
// englishGrade: 92,
|
|
753
|
+
// gradeLevel: '5th',
|
|
754
|
+
// teacher: 'Ms. Johnson',
|
|
755
|
+
// age: 10,
|
|
756
|
+
// scienceGrade: 80,
|
|
757
|
+
// extracurriculars: ['reading'],
|
|
758
|
+
// academicYear: '2024-2025',
|
|
759
|
+
// status: 'active',
|
|
760
|
+
// lastUpdated: '2024-12-23T06:22:05.491Z',
|
|
761
|
+
// fullName: 'Alice Smith',
|
|
762
|
+
// gradeAverage: 86
|
|
763
|
+
// }
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
### Example 8: File Conversion
|
|
231
767
|
|
|
232
768
|
```javascript
|
|
233
769
|
// Convert YAML file to JSON structure
|
|
@@ -248,11 +784,15 @@ await result.save('./config.json');
|
|
|
248
784
|
## 🎯 Key Features
|
|
249
785
|
|
|
250
786
|
- ✅ **Simple API**: Just map fields and convert
|
|
787
|
+
- ✅ **Pipeline Architecture**: Robust internal processing with configurable steps and error handling
|
|
251
788
|
- ✅ **No direct Mapper usage needed**: The `convert()` method handles everything
|
|
252
789
|
- ✅ **Nested object support**: Use dot notation like `user.profile.name`
|
|
253
790
|
- ✅ **Array access**: Use bracket notation like `items[0]`
|
|
254
791
|
- ✅ **Transformations**: Transform values during mapping
|
|
792
|
+
- ✅ **Conditional mapping**: Use `when()` for conditional logic
|
|
793
|
+
- ✅ **Multi-field merging**: Use `merge()` to combine multiple sources into one target
|
|
255
794
|
- ✅ **Default values**: Set fallback values
|
|
795
|
+
- ✅ **Comprehensive Error Handling**: Advanced error reporting with context and suggestions ⭐ NEW!
|
|
256
796
|
- ✅ **File support**: Convert YAML, JSON files directly
|
|
257
797
|
- ✅ **Statistics**: Get detailed conversion reports
|
|
258
798
|
- ✅ **TypeScript**: Full type safety
|
|
@@ -264,16 +804,19 @@ await result.save('./config.json');
|
|
|
264
804
|
- Basic field mapping with `map()`
|
|
265
805
|
- Nested object and array access
|
|
266
806
|
- Value transformations
|
|
267
|
-
- Default values with `defaults()
|
|
807
|
+
- **Default values with `defaults()`** ⭐ ENHANCED!
|
|
808
|
+
- **Array/object processing with `forEach()`**
|
|
809
|
+
- **Conditional mapping with `when()`**
|
|
810
|
+
- **Multi-field merging with `merge()`**
|
|
811
|
+
- **Field validation with `validate()`**
|
|
812
|
+
- **Lifecycle hooks with `before()` and `after()`** ⭐ NEW!
|
|
813
|
+
- **Advanced error handling and reporting system** ⭐ NEW!
|
|
268
814
|
- File parsing (YAML, JSON)
|
|
269
815
|
- Conversion statistics and reporting
|
|
270
|
-
-
|
|
816
|
+
- Async and sync conversion support
|
|
271
817
|
|
|
272
818
|
**🚧 Coming Soon:**
|
|
273
819
|
|
|
274
|
-
- Conditional mapping with `when()`
|
|
275
|
-
- Array processing with `forEach()`
|
|
276
|
-
- Field validation
|
|
277
820
|
- CLI generation
|
|
278
821
|
- Plugin system
|
|
279
822
|
|
|
@@ -283,9 +826,29 @@ await result.save('./config.json');
|
|
|
283
826
|
2. **Use dot notation** for nested objects: `'user.profile.name'`
|
|
284
827
|
3. **Use bracket notation** for arrays: `'items[0]'`, `'items[1]'`
|
|
285
828
|
4. **Transform functions** get the value and context: `(value, ctx) => { ... }`
|
|
286
|
-
5. **
|
|
287
|
-
6. **Use `result
|
|
829
|
+
5. **Use conditional mapping** with `when()` for type-specific logic: `when(source => source.accountType === 'premium')`
|
|
830
|
+
6. **Use merge()** to combine multiple fields: `merge(['field1', 'field2'], 'result', (a, b) => a + b)`
|
|
831
|
+
7. **Use validation** to ensure data quality: `validate('email', validators.email)`
|
|
832
|
+
8. **Combine validators** with `validators.all()` for multiple rules
|
|
833
|
+
9. **Always call `.end()`** after conditional mappings to return to the main converter
|
|
834
|
+
10. **Check `result.errors`** to see validation failures
|
|
835
|
+
11. **Check `result.unmapped`** to see which fields weren't mapped
|
|
836
|
+
12. **Use `result.print()`** for a nice conversion report
|
|
837
|
+
13. **Use `defaults()`** to provide fallback values for missing fields: `defaults({ status: 'active' })`
|
|
838
|
+
14. **Use function defaults** for dynamic values: `defaults({ timestamp: () => new Date().toISOString() })`
|
|
839
|
+
15. **Use `before()` hooks** to preprocess source data before conversion
|
|
840
|
+
16. **Use `after()` hooks** to postprocess target data after conversion
|
|
841
|
+
17. **Hooks can be async** - just use `async (data) => { ... }` and they'll be awaited
|
|
842
|
+
18. **Multiple hooks execute in order** - add as many as you need for complex workflows
|
|
843
|
+
19. **Error handling provides helpful suggestions** - when field mapping fails, you'll get suggestions for similar field names
|
|
844
|
+
20. **Errors include rich context** - see exactly where and why conversions failed with detailed error information
|
|
845
|
+
21. **Use error categories** to filter and handle different types of errors (validation, mapping, parsing, etc.)
|
|
288
846
|
|
|
289
847
|
---
|
|
290
848
|
|
|
291
849
|
That's it! ConfigForge makes config conversion simple and straightforward. No complex setup, no direct class manipulation - just define your mappings and convert.
|
|
850
|
+
|
|
851
|
+
## License
|
|
852
|
+
|
|
853
|
+
This package is licensed under the **PolyForm Noncommercial License 1.0.0**.
|
|
854
|
+
See the [LICENSE](./LICENSE) file for full terms.
|
|
@@ -8,6 +8,8 @@ export declare class ConversionResultImpl implements ConversionResult {
|
|
|
8
8
|
unmapped: string[];
|
|
9
9
|
errors: ConfigForgeError[];
|
|
10
10
|
stats: ConversionStats;
|
|
11
|
+
private errorCollector;
|
|
12
|
+
private errorReporter;
|
|
11
13
|
constructor(data: any, warnings: Warning[] | undefined, unmapped: string[] | undefined, errors: ConfigForgeError[] | undefined, stats: ConversionStats);
|
|
12
14
|
/**
|
|
13
15
|
* Save the converted data to a file
|
|
@@ -25,6 +27,26 @@ export declare class ConversionResultImpl implements ConversionResult {
|
|
|
25
27
|
* Pretty print conversion results to console
|
|
26
28
|
*/
|
|
27
29
|
print(): void;
|
|
30
|
+
/**
|
|
31
|
+
* Get detailed error report
|
|
32
|
+
*/
|
|
33
|
+
getErrorReport(format?: 'text' | 'json' | 'summary'): string;
|
|
34
|
+
/**
|
|
35
|
+
* Check if conversion was successful (no critical errors)
|
|
36
|
+
*/
|
|
37
|
+
isSuccess(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Get error summary
|
|
40
|
+
*/
|
|
41
|
+
getErrorSummary(): string;
|
|
42
|
+
/**
|
|
43
|
+
* Get errors grouped by type
|
|
44
|
+
*/
|
|
45
|
+
getErrorsByType(): Record<string, ConfigForgeError[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Get warnings grouped by type
|
|
48
|
+
*/
|
|
49
|
+
getWarningsByType(): Record<string, Warning[]>;
|
|
28
50
|
/**
|
|
29
51
|
* Get file extension from path
|
|
30
52
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConversionResult.d.ts","sourceRoot":"","sources":["../../src/core/ConversionResult.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,OAAO,EACP,gBAAgB,EACjB,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"ConversionResult.d.ts","sourceRoot":"","sources":["../../src/core/ConversionResult.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,OAAO,EACP,gBAAgB,EACjB,MAAM,UAAU,CAAC;AAKlB;;GAEG;AACH,qBAAa,oBAAqB,YAAW,gBAAgB;IAKlD,IAAI,EAAE,GAAG;IACT,QAAQ,EAAE,OAAO,EAAE;IACnB,QAAQ,EAAE,MAAM,EAAE;IAClB,MAAM,EAAE,gBAAgB,EAAE;IAC1B,KAAK,EAAE,eAAe;IAR/B,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,aAAa,CAAgB;gBAG5B,IAAI,EAAE,GAAG,EACT,QAAQ,EAAE,OAAO,EAAE,YAAK,EACxB,QAAQ,EAAE,MAAM,EAAE,YAAK,EACvB,MAAM,EAAE,gBAAgB,EAAE,YAAK,EAC/B,KAAK,EAAE,eAAe;IAS/B;;OAEG;IACG,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBvC;;OAEG;IACH,MAAM,IAAI,MAAM;IAIhB;;OAEG;IACH,MAAM,IAAI,MAAM;IAQhB;;OAEG;IACH,KAAK,IAAI,IAAI;IAmCb;;OAEG;IACH,cAAc,CAAC,MAAM,GAAE,MAAM,GAAG,MAAM,GAAG,SAAkB,GAAG,MAAM;IAIpE;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,eAAe,IAAI,MAAM;IAIzB;;OAEG;IACH,eAAe,IAAI,MAAM,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAIrD;;OAEG;IACH,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;IAI9C;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAKzB"}
|