configforge 1.0.0-beta.1 → 1.0.0-beta.11
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 +630 -12
- 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 +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -2
- package/dist/index.js.map +1 -1
- package/dist/scripts/CLIGenerator.d.ts +109 -0
- package/dist/scripts/CLIGenerator.d.ts.map +1 -0
- package/dist/scripts/CLIGenerator.js +440 -0
- package/dist/scripts/CLIGenerator.js.map +1 -0
- package/dist/scripts/CLIUtils.d.ts +136 -0
- package/dist/scripts/CLIUtils.d.ts.map +1 -0
- package/dist/scripts/CLIUtils.js +361 -0
- package/dist/scripts/CLIUtils.js.map +1 -0
- package/dist/scripts/CommandBuilder.d.ts +72 -0
- package/dist/scripts/CommandBuilder.d.ts.map +1 -0
- package/dist/scripts/CommandBuilder.js +280 -0
- package/dist/scripts/CommandBuilder.js.map +1 -0
- package/dist/scripts/index.d.ts +23 -0
- package/dist/scripts/index.d.ts.map +1 -0
- package/dist/scripts/index.js +43 -0
- package/dist/scripts/index.js.map +1 -0
- 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
|
|
107
|
+
|
|
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
|
|
90
126
|
|
|
91
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
|
+
})
|
|
97
141
|
```
|
|
98
142
|
|
|
99
|
-
###
|
|
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
|
+
})
|
|
178
|
+
```
|
|
179
|
+
|
|
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
|
|
@@ -245,14 +781,70 @@ const result = await converter.convert('./config.yml');
|
|
|
245
781
|
await result.save('./config.json');
|
|
246
782
|
```
|
|
247
783
|
|
|
784
|
+
### Example 9: CLI Generation System
|
|
785
|
+
|
|
786
|
+
```javascript
|
|
787
|
+
const { forge, CLIGenerator } = require('configforge');
|
|
788
|
+
|
|
789
|
+
// Create your converter
|
|
790
|
+
const converter = forge()
|
|
791
|
+
.from('legacy')
|
|
792
|
+
.to('modern')
|
|
793
|
+
.map('app_name', 'name')
|
|
794
|
+
.map('app_version', 'version')
|
|
795
|
+
.map('database.host', 'db.hostname')
|
|
796
|
+
.map('database.port', 'db.port')
|
|
797
|
+
.defaults({
|
|
798
|
+
environment: 'production',
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
// Generate CLI for your converter
|
|
802
|
+
const cli = CLIGenerator.forConverter(converter, {
|
|
803
|
+
name: 'config-converter',
|
|
804
|
+
version: '1.0.0',
|
|
805
|
+
description: 'Convert legacy config to modern format',
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
// Add custom commands
|
|
809
|
+
cli.addCommand({
|
|
810
|
+
name: 'migrate',
|
|
811
|
+
description: 'Migrate all configs in a directory',
|
|
812
|
+
options: [
|
|
813
|
+
{
|
|
814
|
+
flags: '-d, --directory <dir>',
|
|
815
|
+
description: 'Directory containing config files',
|
|
816
|
+
},
|
|
817
|
+
],
|
|
818
|
+
action: async options => {
|
|
819
|
+
// Custom migration logic
|
|
820
|
+
console.log(`Migrating configs in ${options.directory}`);
|
|
821
|
+
},
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
// Parse command line arguments
|
|
825
|
+
cli.parse();
|
|
826
|
+
|
|
827
|
+
// Now you can use your CLI:
|
|
828
|
+
// $ config-converter convert input.yml output.json
|
|
829
|
+
// $ config-converter validate config.yml
|
|
830
|
+
// $ config-converter profile save my-config
|
|
831
|
+
// $ config-converter profile list
|
|
832
|
+
// $ config-converter migrate -d ./configs
|
|
833
|
+
```
|
|
834
|
+
|
|
248
835
|
## 🎯 Key Features
|
|
249
836
|
|
|
250
837
|
- ✅ **Simple API**: Just map fields and convert
|
|
838
|
+
- ✅ **Pipeline Architecture**: Robust internal processing with configurable steps and error handling
|
|
251
839
|
- ✅ **No direct Mapper usage needed**: The `convert()` method handles everything
|
|
252
840
|
- ✅ **Nested object support**: Use dot notation like `user.profile.name`
|
|
253
841
|
- ✅ **Array access**: Use bracket notation like `items[0]`
|
|
254
842
|
- ✅ **Transformations**: Transform values during mapping
|
|
843
|
+
- ✅ **Conditional mapping**: Use `when()` for conditional logic
|
|
844
|
+
- ✅ **Multi-field merging**: Use `merge()` to combine multiple sources into one target
|
|
255
845
|
- ✅ **Default values**: Set fallback values
|
|
846
|
+
- ✅ **Comprehensive Error Handling**: Advanced error reporting with context and suggestions ⭐ NEW!
|
|
847
|
+
- ✅ **CLI Generation System**: Create command-line interfaces for converters ⭐ NEW!
|
|
256
848
|
- ✅ **File support**: Convert YAML, JSON files directly
|
|
257
849
|
- ✅ **Statistics**: Get detailed conversion reports
|
|
258
850
|
- ✅ **TypeScript**: Full type safety
|
|
@@ -264,17 +856,20 @@ await result.save('./config.json');
|
|
|
264
856
|
- Basic field mapping with `map()`
|
|
265
857
|
- Nested object and array access
|
|
266
858
|
- Value transformations
|
|
267
|
-
- Default values with `defaults()
|
|
859
|
+
- **Default values with `defaults()`** ⭐ ENHANCED!
|
|
860
|
+
- **Array/object processing with `forEach()`**
|
|
861
|
+
- **Conditional mapping with `when()`**
|
|
862
|
+
- **Multi-field merging with `merge()`**
|
|
863
|
+
- **Field validation with `validate()`**
|
|
864
|
+
- **Lifecycle hooks with `before()` and `after()`** ⭐ NEW!
|
|
865
|
+
- **Advanced error handling and reporting system** ⭐ NEW!
|
|
866
|
+
- **CLI generation system with command-line interfaces** ⭐ ENHANCED!
|
|
268
867
|
- File parsing (YAML, JSON)
|
|
269
868
|
- Conversion statistics and reporting
|
|
270
|
-
-
|
|
869
|
+
- Async and sync conversion support
|
|
271
870
|
|
|
272
871
|
**🚧 Coming Soon:**
|
|
273
872
|
|
|
274
|
-
- Conditional mapping with `when()`
|
|
275
|
-
- Array processing with `forEach()`
|
|
276
|
-
- Field validation
|
|
277
|
-
- CLI generation
|
|
278
873
|
- Plugin system
|
|
279
874
|
|
|
280
875
|
## 💡 Tips
|
|
@@ -283,9 +878,32 @@ await result.save('./config.json');
|
|
|
283
878
|
2. **Use dot notation** for nested objects: `'user.profile.name'`
|
|
284
879
|
3. **Use bracket notation** for arrays: `'items[0]'`, `'items[1]'`
|
|
285
880
|
4. **Transform functions** get the value and context: `(value, ctx) => { ... }`
|
|
286
|
-
5. **
|
|
287
|
-
6. **Use `result
|
|
881
|
+
5. **Use conditional mapping** with `when()` for type-specific logic: `when(source => source.accountType === 'premium')`
|
|
882
|
+
6. **Use merge()** to combine multiple fields: `merge(['field1', 'field2'], 'result', (a, b) => a + b)`
|
|
883
|
+
7. **Use validation** to ensure data quality: `validate('email', validators.email)`
|
|
884
|
+
8. **Combine validators** with `validators.all()` for multiple rules
|
|
885
|
+
9. **Always call `.end()`** after conditional mappings to return to the main converter
|
|
886
|
+
10. **Check `result.errors`** to see validation failures
|
|
887
|
+
11. **Check `result.unmapped`** to see which fields weren't mapped
|
|
888
|
+
12. **Use `result.print()`** for a nice conversion report
|
|
889
|
+
13. **Use `defaults()`** to provide fallback values for missing fields: `defaults({ status: 'active' })`
|
|
890
|
+
14. **Use function defaults** for dynamic values: `defaults({ timestamp: () => new Date().toISOString() })`
|
|
891
|
+
15. **Use `before()` hooks** to preprocess source data before conversion
|
|
892
|
+
16. **Use `after()` hooks** to postprocess target data after conversion
|
|
893
|
+
17. **Hooks can be async** - just use `async (data) => { ... }` and they'll be awaited
|
|
894
|
+
18. **Multiple hooks execute in order** - add as many as you need for complex workflows
|
|
895
|
+
19. **Error handling provides helpful suggestions** - when field mapping fails, you'll get suggestions for similar field names
|
|
896
|
+
20. **Errors include rich context** - see exactly where and why conversions failed with detailed error information
|
|
897
|
+
21. **Use error categories** to filter and handle different types of errors (validation, mapping, parsing, etc.)
|
|
898
|
+
22. **Create CLIs for converters** - use `CLIGenerator.forConverter(converter)` to generate command-line interfaces
|
|
899
|
+
23. **Use CLI profiles** - save converter configurations as profiles for reuse: `cli profile save my-converter`
|
|
900
|
+
24. **CLI supports batch processing** - convert multiple files at once with pattern matching and parallel processing
|
|
288
901
|
|
|
289
902
|
---
|
|
290
903
|
|
|
291
904
|
That's it! ConfigForge makes config conversion simple and straightforward. No complex setup, no direct class manipulation - just define your mappings and convert.
|
|
905
|
+
|
|
906
|
+
## License
|
|
907
|
+
|
|
908
|
+
This package is licensed under the **PolyForm Noncommercial License 1.0.0**.
|
|
909
|
+
See the [LICENSE](./LICENSE) file for full terms.
|