@tamyla/clodo-framework 2.0.1 → 2.0.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.
@@ -0,0 +1,1230 @@
1
+ /**
2
+ * Enterprise Configuration Cache Manager
3
+ *
4
+ * Advanced configuration management system for multi-domain deployments with:
5
+ * - Smart configuration caching with TTL and invalidation
6
+ * - Template-based configuration generation
7
+ * - Runtime configuration discovery and validation
8
+ * - Multi-environment configuration coordination
9
+ * - Configuration versioning and rollback capabilities
10
+ * - Performance-optimized caching with compression
11
+ * - Cross-domain configuration inheritance
12
+ * - Configuration backup and restore
13
+ * - Real-time configuration updates
14
+ * - Compliance and audit trail for config changes
15
+ *
16
+ * @module config-cache
17
+ * @version 2.0.0
18
+ */
19
+
20
+ import { access, writeFile, readFile, mkdir, readdir, stat } from 'fs/promises';
21
+ import { join, dirname, basename } from 'path';
22
+ import { exec } from 'child_process';
23
+ import { promisify } from 'util';
24
+
25
+ const execAsync = promisify(exec);
26
+
27
+ export class ConfigurationCacheManager {
28
+ constructor(options = {}) {
29
+ this.config = {
30
+ // Cache configuration
31
+ cacheDir: options.cacheDir || 'config-cache',
32
+ cacheTTL: options.cacheTTL || 3600000, // 1 hour in ms
33
+ maxCacheSize: options.maxCacheSize || 50 * 1024 * 1024, // 50MB
34
+ enableCompression: options.enableCompression !== false,
35
+
36
+ // Template configuration
37
+ templateDir: options.templateDir || 'config-templates',
38
+ enableTemplateInheritance: options.enableTemplateInheritance !== false,
39
+ templateVersioning: options.templateVersioning !== false,
40
+
41
+ // Runtime discovery
42
+ enableRuntimeDiscovery: options.enableRuntimeDiscovery !== false,
43
+ discoveryTimeout: options.discoveryTimeout || 30000,
44
+ cloudflareCaching: options.cloudflareCaching !== false,
45
+
46
+ // Environments
47
+ environments: options.environments || ['development', 'staging', 'production'],
48
+ defaultEnvironment: options.defaultEnvironment || 'production',
49
+
50
+ // Validation and backups
51
+ enableValidation: options.enableValidation !== false,
52
+ enableBackups: options.enableBackups !== false,
53
+ backupRetentionDays: options.backupRetentionDays || 30,
54
+
55
+ // Performance
56
+ enableMetrics: options.enableMetrics !== false,
57
+ preloadConfigs: options.preloadConfigs || [],
58
+
59
+ // Cross-domain features
60
+ enableCrossDomainSharing: options.enableCrossDomainSharing !== false,
61
+ sharedConfigKeys: options.sharedConfigKeys || ['features', 'settings.security'],
62
+
63
+ // Output formats
64
+ outputFormats: options.outputFormats || ['json', 'js', 'yaml', 'env']
65
+ };
66
+
67
+ // Cache state
68
+ this.cache = new Map();
69
+ this.templates = new Map();
70
+ this.metrics = {
71
+ cacheHits: 0,
72
+ cacheMisses: 0,
73
+ discoveryRequests: 0,
74
+ validationErrors: 0,
75
+ templateGenerations: 0
76
+ };
77
+
78
+ // Configuration registry
79
+ this.configRegistry = {
80
+ domains: new Map(),
81
+ templates: new Map(),
82
+ shared: new Map()
83
+ };
84
+
85
+ // Built-in configuration templates
86
+ this.builtinTemplates = {
87
+ 'domain-standard': this.getStandardDomainTemplate(),
88
+ 'domain-minimal': this.getMinimalDomainTemplate(),
89
+ 'domain-enterprise': this.getEnterpriseDomainTemplate(),
90
+ 'cloudflare-worker': this.getCloudflareWorkerTemplate(),
91
+ 'database-standard': this.getStandardDatabaseTemplate()
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Initialize the configuration cache manager
97
+ * @returns {Promise<void>}
98
+ */
99
+ async initialize() {
100
+ await this.initializeCacheSystem();
101
+ this.loadBuiltinTemplates();
102
+
103
+ console.log('🎛️ Configuration Cache Manager initialized');
104
+ if (this.config.enableMetrics) {
105
+ console.log(` 💾 Cache Directory: ${this.config.cacheDir}`);
106
+ console.log(` ⏰ Cache TTL: ${this.config.cacheTTL}ms`);
107
+ console.log(` 📋 Templates: ${Object.keys(this.builtinTemplates).length} builtin`);
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Initialize cache system directories and structures
113
+ * @returns {Promise<void>}
114
+ */
115
+ async initializeCacheSystem() {
116
+ this.paths = {
117
+ cache: this.config.cacheDir,
118
+ templates: this.config.templateDir,
119
+ backups: join(this.config.cacheDir, 'backups'),
120
+ versions: join(this.config.cacheDir, 'versions'),
121
+ shared: join(this.config.cacheDir, 'shared'),
122
+ runtime: join(this.config.cacheDir, 'runtime'),
123
+ metrics: join(this.config.cacheDir, 'metrics')
124
+ };
125
+
126
+ // Create directory structure
127
+ for (const path of Object.values(this.paths)) {
128
+ try {
129
+ await access(path);
130
+ } catch {
131
+ await mkdir(path, { recursive: true });
132
+ }
133
+ }
134
+
135
+ // Initialize cache metadata
136
+ this.cacheMetadataFile = join(this.paths.cache, 'cache-metadata.json');
137
+ await this.loadCacheMetadata();
138
+ }
139
+
140
+ /**
141
+ * Load cache metadata from disk
142
+ * @returns {Promise<void>}
143
+ */
144
+ async loadCacheMetadata() {
145
+ try {
146
+ await access(this.cacheMetadataFile);
147
+ const metadataContent = await readFile(this.cacheMetadataFile, 'utf8');
148
+ const metadata = JSON.parse(metadataContent);
149
+ this.cacheMetadata = {
150
+ ...metadata,
151
+ loadedAt: new Date()
152
+ };
153
+ } catch (error) {
154
+ if (error.code !== 'ENOENT') {
155
+ console.warn('⚠️ Failed to load cache metadata, initializing new');
156
+ }
157
+ this.cacheMetadata = this.createEmptyMetadata();
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Create empty cache metadata structure
163
+ * @returns {Object} Empty metadata
164
+ */
165
+ createEmptyMetadata() {
166
+ return {
167
+ version: '2.0.0',
168
+ createdAt: new Date(),
169
+ lastUpdate: new Date(),
170
+ cacheEntries: {},
171
+ templateVersions: {},
172
+ sharedConfigs: {}
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Save cache metadata to disk
178
+ * @returns {Promise<void>}
179
+ */
180
+ async saveCacheMetadata() {
181
+ this.cacheMetadata.lastUpdate = new Date();
182
+ await writeFile(this.cacheMetadataFile, JSON.stringify(this.cacheMetadata, null, 2));
183
+ }
184
+
185
+ /**
186
+ * Load builtin configuration templates
187
+ */
188
+ loadBuiltinTemplates() {
189
+ Object.entries(this.builtinTemplates).forEach(([name, template]) => {
190
+ this.templates.set(name, {
191
+ ...template,
192
+ builtin: true,
193
+ loadedAt: new Date()
194
+ });
195
+ });
196
+ }
197
+
198
+ /**
199
+ * Get or create configuration for a domain
200
+ * @param {string} domain - Domain name
201
+ * @param {Object} options - Configuration options
202
+ * @returns {Promise<Object>} Domain configuration
203
+ */
204
+ async getOrCreateDomainConfig(domain, options = {}) {
205
+ console.log(`🔍 Getting configuration for domain: ${domain}`);
206
+
207
+ const cacheKey = this.generateCacheKey(domain, options.environment || this.config.defaultEnvironment);
208
+
209
+ // Check cache first
210
+ if (this.isCacheValid(cacheKey)) {
211
+ this.metrics.cacheHits++;
212
+ console.log(` 💾 Cache hit for ${domain}`);
213
+ return await this.getCachedConfig(cacheKey);
214
+ }
215
+
216
+ this.metrics.cacheMisses++;
217
+ console.log(` 🔄 Cache miss, generating configuration for ${domain}`);
218
+
219
+ try {
220
+ let domainConfig;
221
+
222
+ // Check if we have a saved configuration
223
+ const savedConfig = await this.loadSavedConfig(domain, options.environment);
224
+ if (savedConfig && !options.forceRefresh) {
225
+ domainConfig = savedConfig;
226
+ } else if (this.config.enableRuntimeDiscovery && options.cloudflareToken) {
227
+ // Runtime discovery
228
+ domainConfig = await this.discoverDomainConfiguration(domain, options);
229
+ } else {
230
+ // Generate from template
231
+ domainConfig = await this.generateFromTemplate(domain, options);
232
+ }
233
+
234
+ // Validate configuration
235
+ if (this.config.enableValidation) {
236
+ const validation = await this.validateConfiguration(domainConfig);
237
+ if (!validation.valid) {
238
+ throw new Error(`Configuration validation failed: ${validation.errors.join(', ')}`);
239
+ }
240
+ }
241
+
242
+ // Cache the configuration
243
+ await this.cacheConfiguration(cacheKey, domainConfig);
244
+
245
+ // Save for future use
246
+ await this.saveConfiguration(domain, domainConfig, options.environment);
247
+
248
+ return domainConfig;
249
+
250
+ } catch (error) {
251
+ console.error(`❌ Failed to get configuration for ${domain}: ${error.message}`);
252
+ throw error;
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Discover domain configuration from Cloudflare API
258
+ * @param {string} domain - Domain name
259
+ * @param {Object} options - Discovery options
260
+ * @returns {Promise<Object>} Discovered configuration
261
+ */
262
+ async discoverDomainConfiguration(domain, options = {}) {
263
+ console.log(` 🔍 Discovering Cloudflare configuration for ${domain}`);
264
+ this.metrics.discoveryRequests++;
265
+
266
+ const { cloudflareToken, environment = this.config.defaultEnvironment } = options;
267
+
268
+ if (!cloudflareToken) {
269
+ throw new Error('Cloudflare token required for runtime discovery');
270
+ }
271
+
272
+ try {
273
+ // Get Cloudflare account information
274
+ const accountInfo = await this.fetchCloudflareAccounts(cloudflareToken);
275
+ const zoneInfo = await this.fetchCloudflareZone(domain, cloudflareToken);
276
+
277
+ // Build configuration from discovered data
278
+ const discoveredConfig = {
279
+ domain,
280
+ environment,
281
+
282
+ // Cloudflare metadata
283
+ cloudflare: {
284
+ accountId: accountInfo.id,
285
+ accountName: accountInfo.name,
286
+ zoneId: zoneInfo.id,
287
+ zoneName: zoneInfo.name,
288
+ discoveredAt: new Date().toISOString()
289
+ },
290
+
291
+ // Generate standard configuration structure
292
+ ...this.generateStandardConfig(domain, accountInfo, zoneInfo),
293
+
294
+ // Mark as dynamically discovered
295
+ metadata: {
296
+ type: 'discovered',
297
+ source: 'cloudflare-api',
298
+ timestamp: new Date(),
299
+ version: '1.0.0'
300
+ }
301
+ };
302
+
303
+ console.log(` ✅ Successfully discovered configuration for ${domain}`);
304
+ return discoveredConfig;
305
+
306
+ } catch (error) {
307
+ console.error(` ❌ Discovery failed for ${domain}: ${error.message}`);
308
+ throw error;
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Generate configuration from template
314
+ * @param {string} domain - Domain name
315
+ * @param {Object} options - Generation options
316
+ * @returns {Promise<Object>} Generated configuration
317
+ */
318
+ async generateFromTemplate(domain, options = {}) {
319
+ console.log(` 📋 Generating configuration from template for ${domain}`);
320
+ this.metrics.templateGenerations++;
321
+
322
+ const {
323
+ templateName = 'domain-standard',
324
+ environment = this.config.defaultEnvironment,
325
+ customValues = {}
326
+ } = options;
327
+
328
+ // Get template
329
+ const template = this.getTemplate(templateName);
330
+ if (!template) {
331
+ throw new Error(`Template not found: ${templateName}`);
332
+ }
333
+
334
+ // Process template with domain-specific values
335
+ const templateValues = {
336
+ domain,
337
+ environment,
338
+ domainKey: this.getDomainKey(domain),
339
+ displayName: this.getDisplayName(domain),
340
+ timestamp: new Date().toISOString(),
341
+ ...customValues
342
+ };
343
+
344
+ const generatedConfig = await this.processTemplate(template, templateValues);
345
+
346
+ // Apply cross-domain sharing if enabled
347
+ if (this.config.enableCrossDomainSharing) {
348
+ await this.applyCrossDomainSharing(generatedConfig);
349
+ }
350
+
351
+ generatedConfig.metadata = {
352
+ type: 'generated',
353
+ source: `template:${templateName}`,
354
+ timestamp: new Date(),
355
+ version: template.version || '1.0.0'
356
+ };
357
+
358
+ console.log(` ✅ Generated configuration from template: ${templateName}`);
359
+ return generatedConfig;
360
+ }
361
+
362
+ /**
363
+ * Process template with values
364
+ * @param {Object} template - Template object
365
+ * @param {Object} values - Template values
366
+ * @returns {Promise<Object>} Processed configuration
367
+ */
368
+ async processTemplate(template, values) {
369
+ // Deep clone template structure
370
+ const config = JSON.parse(JSON.stringify(template.structure || template));
371
+
372
+ // Process template variables
373
+ const processValue = (obj) => {
374
+ if (typeof obj === 'string') {
375
+ return obj.replace(/\{\{(\w+)\}\}/g, (match, key) => {
376
+ return values[key] || match;
377
+ });
378
+ } else if (Array.isArray(obj)) {
379
+ return obj.map(processValue);
380
+ } else if (obj && typeof obj === 'object') {
381
+ const processed = {};
382
+ for (const [key, value] of Object.entries(obj)) {
383
+ processed[key] = processValue(value);
384
+ }
385
+ return processed;
386
+ }
387
+ return obj;
388
+ };
389
+
390
+ return processValue(config);
391
+ }
392
+
393
+ /**
394
+ * Apply cross-domain configuration sharing
395
+ * @param {Object} config - Configuration to enhance
396
+ */
397
+ async applyCrossDomainSharing(config) {
398
+ const sharedConfigs = await this.getSharedConfigurations();
399
+
400
+ for (const sharedKey of this.config.sharedConfigKeys) {
401
+ if (sharedConfigs[sharedKey]) {
402
+ this.setNestedValue(config, sharedKey, sharedConfigs[sharedKey]);
403
+ }
404
+ }
405
+ }
406
+
407
+ /**
408
+ * Get shared configurations
409
+ * @returns {Promise<Object>} Shared configurations
410
+ */
411
+ async getSharedConfigurations() {
412
+ const sharedFile = join(this.paths.shared, 'shared-configs.json');
413
+
414
+ try {
415
+ await access(sharedFile);
416
+ const content = await readFile(sharedFile, 'utf8');
417
+ return JSON.parse(content);
418
+ } catch (error) {
419
+ if (error.code !== 'ENOENT') {
420
+ console.warn('⚠️ Failed to load shared configurations');
421
+ }
422
+ return {};
423
+ }
424
+ }
425
+
426
+ /**
427
+ * Validate configuration object
428
+ * @param {Object} config - Configuration to validate
429
+ * @returns {Promise<Object>} Validation result
430
+ */
431
+ async validateConfiguration(config) {
432
+ const errors = [];
433
+ const warnings = [];
434
+
435
+ // Basic structure validation
436
+ if (!config.domain) errors.push('Missing domain');
437
+ if (!config.environment) errors.push('Missing environment');
438
+
439
+ // Cloudflare validation
440
+ if (config.cloudflare) {
441
+ if (!config.cloudflare.accountId) errors.push('Missing Cloudflare account ID');
442
+ if (!config.cloudflare.zoneId) errors.push('Missing Cloudflare zone ID');
443
+ }
444
+
445
+ // Services validation
446
+ if (config.services) {
447
+ for (const [serviceName, serviceConfig] of Object.entries(config.services)) {
448
+ if (!serviceConfig[config.environment]) {
449
+ warnings.push(`Missing ${serviceName} configuration for ${config.environment}`);
450
+ }
451
+ }
452
+ }
453
+
454
+ // Database validation
455
+ if (config.databases && config.databases[config.environment]) {
456
+ const dbConfig = config.databases[config.environment];
457
+ if (!dbConfig.name) errors.push(`Missing database name for ${config.environment}`);
458
+ }
459
+
460
+ // CORS validation
461
+ if (config.corsOrigins && config.corsOrigins[config.environment]) {
462
+ const corsOrigins = config.corsOrigins[config.environment];
463
+ if (!Array.isArray(corsOrigins) || corsOrigins.length === 0) {
464
+ warnings.push(`No CORS origins defined for ${config.environment}`);
465
+ }
466
+ }
467
+
468
+ if (errors.length > 0) {
469
+ this.metrics.validationErrors++;
470
+ }
471
+
472
+ return {
473
+ valid: errors.length === 0,
474
+ errors,
475
+ warnings
476
+ };
477
+ }
478
+
479
+ /**
480
+ * Cache configuration with TTL
481
+ * @param {string} cacheKey - Cache key
482
+ * @param {Object} config - Configuration to cache
483
+ */
484
+ async cacheConfiguration(cacheKey, config) {
485
+ const cacheEntry = {
486
+ config,
487
+ cachedAt: new Date(),
488
+ expiresAt: new Date(Date.now() + this.config.cacheTTL),
489
+ size: JSON.stringify(config).length
490
+ };
491
+
492
+ // Compress if enabled
493
+ if (this.config.enableCompression && cacheEntry.size > 1024) {
494
+ // In a real implementation, you'd use a compression library
495
+ cacheEntry.compressed = true;
496
+ cacheEntry.originalSize = cacheEntry.size;
497
+ }
498
+
499
+ this.cache.set(cacheKey, cacheEntry);
500
+
501
+ // Update metadata
502
+ this.cacheMetadata.cacheEntries[cacheKey] = {
503
+ cachedAt: cacheEntry.cachedAt,
504
+ expiresAt: cacheEntry.expiresAt,
505
+ size: cacheEntry.size
506
+ };
507
+
508
+ await this.saveCacheMetadata();
509
+
510
+ // Write to disk cache
511
+ await this.writeToDiskCache(cacheKey, cacheEntry);
512
+
513
+ console.log(` 💾 Cached configuration: ${cacheKey}`);
514
+ }
515
+
516
+ /**
517
+ * Check if cache entry is valid
518
+ * @param {string} cacheKey - Cache key
519
+ * @returns {boolean} True if cache is valid
520
+ */
521
+ isCacheValid(cacheKey) {
522
+ const cacheEntry = this.cache.get(cacheKey);
523
+ if (!cacheEntry) return false;
524
+
525
+ return new Date() < cacheEntry.expiresAt;
526
+ }
527
+
528
+ /**
529
+ * Get cached configuration
530
+ * @param {string} cacheKey - Cache key
531
+ * @returns {Promise<Object>} Cached configuration
532
+ */
533
+ async getCachedConfig(cacheKey) {
534
+ const cacheEntry = this.cache.get(cacheKey);
535
+
536
+ if (cacheEntry) {
537
+ return cacheEntry.config;
538
+ }
539
+
540
+ // Try to load from disk cache
541
+ return await this.loadFromDiskCache(cacheKey);
542
+ }
543
+
544
+ /**
545
+ * Write cache entry to disk
546
+ * @param {string} cacheKey - Cache key
547
+ * @param {Object} cacheEntry - Cache entry
548
+ */
549
+ async writeToDiskCache(cacheKey, cacheEntry) {
550
+ try {
551
+ const cacheFile = join(this.paths.cache, `${cacheKey}.json`);
552
+ await writeFile(cacheFile, JSON.stringify(cacheEntry, null, 2));
553
+ } catch (error) {
554
+ console.warn(`⚠️ Failed to write disk cache: ${error.message}`);
555
+ }
556
+ }
557
+
558
+ /**
559
+ * Load configuration from disk cache
560
+ * @param {string} cacheKey - Cache key
561
+ * @returns {Promise<Object|null>} Cached configuration or null
562
+ */
563
+ async loadFromDiskCache(cacheKey) {
564
+ try {
565
+ const cacheFile = join(this.paths.cache, `${cacheKey}.json`);
566
+
567
+ await access(cacheFile);
568
+ const content = await readFile(cacheFile, 'utf8');
569
+ const cacheEntry = JSON.parse(content);
570
+
571
+ // Check expiration
572
+ if (new Date() < new Date(cacheEntry.expiresAt)) {
573
+ this.cache.set(cacheKey, cacheEntry);
574
+ return cacheEntry.config;
575
+ }
576
+ } catch (error) {
577
+ if (error.code !== 'ENOENT') {
578
+ console.warn(`⚠️ Failed to load disk cache: ${error.message}`);
579
+ }
580
+ }
581
+
582
+ return null;
583
+ }
584
+
585
+ /**
586
+ * Save configuration to persistent storage
587
+ * @param {string} domain - Domain name
588
+ * @param {Object} config - Configuration to save
589
+ * @param {string} environment - Environment
590
+ */
591
+ async saveConfiguration(domain, config, environment) {
592
+ const configDir = join(this.paths.cache, 'domains', domain);
593
+ try {
594
+ await access(configDir);
595
+ } catch {
596
+ await mkdir(configDir, { recursive: true });
597
+ }
598
+
599
+ const configFile = join(configDir, `${environment || 'default'}.json`);
600
+
601
+ // Create backup if file exists
602
+ try {
603
+ await access(configFile);
604
+ if (this.config.enableBackups) {
605
+ await this.createConfigBackup(configFile);
606
+ }
607
+ } catch {
608
+ // File doesn't exist, no backup needed
609
+ }
610
+
611
+ // Save configuration with metadata
612
+ const saveData = {
613
+ ...config,
614
+ savedAt: new Date(),
615
+ version: config.version || '1.0.0'
616
+ };
617
+
618
+ await writeFile(configFile, JSON.stringify(saveData, null, 2));
619
+
620
+ // Generate additional formats if requested
621
+ await this.generateOutputFormats(domain, config, environment);
622
+
623
+ console.log(` 💾 Saved configuration: ${domain}/${environment}`);
624
+ }
625
+
626
+ /**
627
+ * Load saved configuration
628
+ * @param {string} domain - Domain name
629
+ * @param {string} environment - Environment
630
+ * @returns {Promise<Object|null>} Saved configuration or null
631
+ */
632
+ async loadSavedConfig(domain, environment) {
633
+ const configFile = join(this.paths.cache, 'domains', domain, `${environment || 'default'}.json`);
634
+
635
+ try {
636
+ await access(configFile);
637
+ const content = await readFile(configFile, 'utf8');
638
+ const config = JSON.parse(content);
639
+
640
+ // Check if config is still valid (not too old)
641
+ const savedAt = new Date(config.savedAt);
642
+ const maxAge = 24 * 60 * 60 * 1000; // 24 hours
643
+
644
+ if (Date.now() - savedAt.getTime() < maxAge) {
645
+ return config;
646
+ }
647
+ } catch (error) {
648
+ if (error.code !== 'ENOENT') {
649
+ console.warn(`⚠️ Failed to load saved config for ${domain}: ${error.message}`);
650
+ }
651
+ }
652
+
653
+ return null;
654
+ }
655
+
656
+ /**
657
+ * Generate output formats for configuration
658
+ * @param {string} domain - Domain name
659
+ * @param {Object} config - Configuration
660
+ * @param {string} environment - Environment
661
+ */
662
+ async generateOutputFormats(domain, config, environment) {
663
+ if (!this.config.outputFormats.length) return;
664
+
665
+ const outputDir = join(this.paths.cache, 'output', domain);
666
+ try {
667
+ await access(outputDir);
668
+ } catch {
669
+ await mkdir(outputDir, { recursive: true });
670
+ }
671
+
672
+ for (const format of this.config.outputFormats) {
673
+ try {
674
+ const fileName = `${environment || 'default'}.${format}`;
675
+ const filePath = join(outputDir, fileName);
676
+
677
+ let content = '';
678
+
679
+ switch (format) {
680
+ case 'json':
681
+ content = JSON.stringify(config, null, 2);
682
+ break;
683
+
684
+ case 'js':
685
+ content = `// Auto-generated configuration for ${domain}
686
+ export const config = ${JSON.stringify(config, null, 2)};
687
+ export default config;`;
688
+ break;
689
+
690
+ case 'yaml':
691
+ content = this.convertToYaml(config);
692
+ break;
693
+
694
+ case 'env':
695
+ content = this.convertToEnv(config);
696
+ break;
697
+
698
+ default:
699
+ console.warn(`⚠️ Unknown output format: ${format}`);
700
+ continue;
701
+ }
702
+
703
+ await writeFile(filePath, content);
704
+ console.log(` 📄 Generated ${format.toUpperCase()}: ${fileName}`);
705
+
706
+ } catch (error) {
707
+ console.error(`❌ Failed to generate ${format} format: ${error.message}`);
708
+ }
709
+ }
710
+ }
711
+
712
+ /**
713
+ * Convert configuration to YAML format
714
+ * @param {Object} config - Configuration object
715
+ * @returns {string} YAML string
716
+ */
717
+ convertToYaml(config) {
718
+ // Simple YAML conversion - in production, use a proper YAML library
719
+ const yamlify = (obj, indent = 0) => {
720
+ const spaces = ' '.repeat(indent);
721
+ let yaml = '';
722
+
723
+ for (const [key, value] of Object.entries(obj)) {
724
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
725
+ yaml += `${spaces}${key}:\n`;
726
+ yaml += yamlify(value, indent + 1);
727
+ } else if (Array.isArray(value)) {
728
+ yaml += `${spaces}${key}:\n`;
729
+ value.forEach(item => {
730
+ yaml += `${spaces} - ${JSON.stringify(item)}\n`;
731
+ });
732
+ } else {
733
+ yaml += `${spaces}${key}: ${JSON.stringify(value)}\n`;
734
+ }
735
+ }
736
+
737
+ return yaml;
738
+ };
739
+
740
+ return `# Auto-generated configuration\n${yamlify(config)}`;
741
+ }
742
+
743
+ /**
744
+ * Convert configuration to environment variables
745
+ * @param {Object} config - Configuration object
746
+ * @returns {string} Environment variables string
747
+ */
748
+ convertToEnv(config) {
749
+ const envVars = [];
750
+
751
+ const flatten = (obj, prefix = '') => {
752
+ for (const [key, value] of Object.entries(obj)) {
753
+ const envKey = (prefix + key).toUpperCase().replace(/[^A-Z0-9]/g, '_');
754
+
755
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
756
+ flatten(value, `${prefix}${key}_`);
757
+ } else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
758
+ envVars.push(`${envKey}=${value}`);
759
+ }
760
+ }
761
+ };
762
+
763
+ flatten(config);
764
+ return envVars.join('\n');
765
+ }
766
+
767
+ /**
768
+ * Create backup of configuration file
769
+ * @param {string} configFile - Configuration file path
770
+ */
771
+ async createConfigBackup(configFile) {
772
+ try {
773
+ const backupDir = join(this.paths.backups, new Date().toISOString().split('T')[0]);
774
+ try {
775
+ await access(backupDir);
776
+ } catch {
777
+ await mkdir(backupDir, { recursive: true });
778
+ }
779
+
780
+ const backupFile = join(backupDir, `${Date.now()}-${basename(configFile)}`);
781
+ const content = await readFile(configFile, 'utf8');
782
+ await writeFile(backupFile, content);
783
+
784
+ console.log(` 💾 Created backup: ${backupFile}`);
785
+ } catch (error) {
786
+ console.warn(`⚠️ Failed to create backup: ${error.message}`);
787
+ }
788
+ }
789
+
790
+ /**
791
+ * Invalidate cache for domain
792
+ * @param {string} domain - Domain name
793
+ * @param {string} environment - Environment (optional)
794
+ * @returns {Promise<void>}
795
+ */
796
+ async invalidateCache(domain, environment = null) {
797
+ const pattern = environment
798
+ ? this.generateCacheKey(domain, environment)
799
+ : domain;
800
+
801
+ let invalidated = 0;
802
+
803
+ for (const [key, entry] of this.cache.entries()) {
804
+ if (key.includes(pattern)) {
805
+ this.cache.delete(key);
806
+ delete this.cacheMetadata.cacheEntries[key];
807
+ invalidated++;
808
+ }
809
+ }
810
+
811
+ await this.saveCacheMetadata();
812
+
813
+ console.log(`🗑️ Invalidated ${invalidated} cache entries for ${domain}${environment ? `/${environment}` : ''}`);
814
+ }
815
+
816
+ /**
817
+ * Get cache statistics
818
+ * @returns {Object} Cache statistics
819
+ */
820
+ getCacheStatistics() {
821
+ const stats = {
822
+ entries: this.cache.size,
823
+ hitRate: this.metrics.cacheHits / (this.metrics.cacheHits + this.metrics.cacheMisses) * 100,
824
+ metrics: { ...this.metrics },
825
+ memory: {
826
+ cacheSize: 0,
827
+ totalEntries: this.cache.size
828
+ }
829
+ };
830
+
831
+ // Calculate memory usage
832
+ for (const [key, entry] of this.cache.entries()) {
833
+ stats.memory.cacheSize += entry.size || 0;
834
+ }
835
+
836
+ return stats;
837
+ }
838
+
839
+ /**
840
+ * Generate cache key
841
+ * @param {string} domain - Domain name
842
+ * @param {string} environment - Environment
843
+ * @returns {string} Cache key
844
+ */
845
+ generateCacheKey(domain, environment) {
846
+ return `${domain}:${environment}`;
847
+ }
848
+
849
+ /**
850
+ * Get domain key (sanitized version)
851
+ * @param {string} domain - Domain name
852
+ * @returns {string} Domain key
853
+ */
854
+ getDomainKey(domain) {
855
+ return domain.replace(/\./g, '').replace(/[^a-zA-Z0-9]/g, '');
856
+ }
857
+
858
+ /**
859
+ * Get display name for domain
860
+ * @param {string} domain - Domain name
861
+ * @returns {string} Display name
862
+ */
863
+ getDisplayName(domain) {
864
+ return domain
865
+ .split('.')[0]
866
+ .split('-')
867
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
868
+ .join(' ');
869
+ }
870
+
871
+ /**
872
+ * Get template by name
873
+ * @param {string} templateName - Template name
874
+ * @returns {Object|null} Template object
875
+ */
876
+ getTemplate(templateName) {
877
+ return this.templates.get(templateName);
878
+ }
879
+
880
+ /**
881
+ * Set nested value in object using dot notation
882
+ * @param {Object} obj - Target object
883
+ * @param {string} path - Path (e.g., 'a.b.c')
884
+ * @param {*} value - Value to set
885
+ */
886
+ setNestedValue(obj, path, value) {
887
+ const keys = path.split('.');
888
+ let current = obj;
889
+
890
+ for (let i = 0; i < keys.length - 1; i++) {
891
+ const key = keys[i];
892
+ if (!(key in current) || typeof current[key] !== 'object') {
893
+ current[key] = {};
894
+ }
895
+ current = current[key];
896
+ }
897
+
898
+ current[keys[keys.length - 1]] = value;
899
+ }
900
+
901
+ // Built-in template definitions
902
+
903
+ /**
904
+ * Get standard domain template
905
+ * @returns {Object} Standard template
906
+ */
907
+ getStandardDomainTemplate() {
908
+ return {
909
+ version: '1.0.0',
910
+ name: 'domain-standard',
911
+ description: 'Standard domain configuration template',
912
+ structure: {
913
+ domain: '{{domain}}',
914
+ environment: '{{environment}}',
915
+ name: '{{domainKey}}',
916
+ displayName: '{{displayName}}',
917
+
918
+ domains: {
919
+ production: '{{domain}}',
920
+ staging: '{{domain}}',
921
+ development: '{{domain}}'
922
+ },
923
+
924
+ services: {
925
+ frontend: {
926
+ production: 'https://{{domain}}',
927
+ staging: 'https://staging.{{domain}}',
928
+ development: 'http://localhost:3000'
929
+ },
930
+ api: {
931
+ production: 'https://{{domainKey}}-data-service.tamylatrading.workers.dev',
932
+ staging: 'https://{{domainKey}}-data-service-staging.tamylatrading.workers.dev',
933
+ development: 'http://localhost:8787'
934
+ },
935
+ auth: {
936
+ production: 'https://auth.{{domain}}',
937
+ staging: 'https://auth-staging.{{domain}}',
938
+ development: 'http://localhost:8787'
939
+ }
940
+ },
941
+
942
+ corsOrigins: {
943
+ production: ['https://{{domain}}', 'https://*.{{domain}}'],
944
+ staging: ['https://staging.{{domain}}', 'https://*.staging.{{domain}}'],
945
+ development: ['http://localhost:3000', 'http://localhost:8787']
946
+ },
947
+
948
+ databases: {
949
+ production: { name: '{{domainKey}}-auth-db', id: null },
950
+ staging: { name: '{{domainKey}}-auth-db-staging', id: null },
951
+ development: { name: '{{domainKey}}-auth-db-local', id: null }
952
+ },
953
+
954
+ features: {
955
+ magicLinkAuth: true,
956
+ fileStorage: true,
957
+ userProfiles: true,
958
+ logging: true,
959
+ webhooks: false
960
+ },
961
+
962
+ settings: {
963
+ magicLinkExpiryMinutes: 15,
964
+ rateLimitWindowMs: 900000,
965
+ rateLimitMax: 100,
966
+ maxFileSize: 26214400,
967
+ allowedFileTypes: [
968
+ 'image/jpeg', 'image/png', 'image/webp', 'image/gif',
969
+ 'application/pdf',
970
+ 'video/mp4', 'video/webm',
971
+ 'audio/mpeg', 'audio/wav'
972
+ ]
973
+ }
974
+ }
975
+ };
976
+ }
977
+
978
+ /**
979
+ * Get minimal domain template
980
+ * @returns {Object} Minimal template
981
+ */
982
+ getMinimalDomainTemplate() {
983
+ return {
984
+ version: '1.0.0',
985
+ name: 'domain-minimal',
986
+ description: 'Minimal domain configuration template',
987
+ structure: {
988
+ domain: '{{domain}}',
989
+ environment: '{{environment}}',
990
+ name: '{{domainKey}}',
991
+
992
+ services: {
993
+ api: {
994
+ production: 'https://{{domainKey}}-data-service.tamylatrading.workers.dev'
995
+ }
996
+ },
997
+
998
+ features: {
999
+ magicLinkAuth: true,
1000
+ logging: false
1001
+ }
1002
+ }
1003
+ };
1004
+ }
1005
+
1006
+ /**
1007
+ * Get enterprise domain template
1008
+ * @returns {Object} Enterprise template
1009
+ */
1010
+ getEnterpriseDomainTemplate() {
1011
+ return {
1012
+ version: '1.0.0',
1013
+ name: 'domain-enterprise',
1014
+ description: 'Enterprise domain configuration template',
1015
+ structure: {
1016
+ domain: '{{domain}}',
1017
+ environment: '{{environment}}',
1018
+ name: '{{domainKey}}',
1019
+ displayName: '{{displayName}}',
1020
+
1021
+ domains: {
1022
+ production: '{{domain}}',
1023
+ staging: 'staging.{{domain}}',
1024
+ development: 'dev.{{domain}}'
1025
+ },
1026
+
1027
+ services: {
1028
+ frontend: {
1029
+ production: 'https://{{domain}}',
1030
+ staging: 'https://staging.{{domain}}',
1031
+ development: 'https://dev.{{domain}}'
1032
+ },
1033
+ api: {
1034
+ production: 'https://api.{{domain}}',
1035
+ staging: 'https://api-staging.{{domain}}',
1036
+ development: 'https://api-dev.{{domain}}'
1037
+ },
1038
+ cdn: {
1039
+ production: 'https://cdn.{{domain}}',
1040
+ staging: 'https://cdn-staging.{{domain}}',
1041
+ development: 'https://cdn-dev.{{domain}}'
1042
+ }
1043
+ },
1044
+
1045
+ features: {
1046
+ magicLinkAuth: true,
1047
+ fileStorage: true,
1048
+ userProfiles: true,
1049
+ logging: true,
1050
+ webhooks: true,
1051
+ analytics: true,
1052
+ monitoring: true,
1053
+ backup: true
1054
+ },
1055
+
1056
+ security: {
1057
+ enableCSP: true,
1058
+ enableHSTS: true,
1059
+ requireHTTPS: true,
1060
+ rateLimiting: true
1061
+ }
1062
+ }
1063
+ };
1064
+ }
1065
+
1066
+ /**
1067
+ * Get Cloudflare Worker template
1068
+ * @returns {Object} Worker template
1069
+ */
1070
+ getCloudflareWorkerTemplate() {
1071
+ return {
1072
+ version: '1.0.0',
1073
+ name: 'cloudflare-worker',
1074
+ description: 'Cloudflare Worker configuration template',
1075
+ structure: {
1076
+ name: '{{domainKey}}-data-service',
1077
+ main: 'src/worker/worker.js',
1078
+ compatibility_date: '2023-12-01',
1079
+
1080
+ env: {
1081
+ production: {
1082
+ vars: {
1083
+ ENVIRONMENT: 'production',
1084
+ CORS_ORIGIN: 'https://{{domain}}'
1085
+ }
1086
+ }
1087
+ }
1088
+ }
1089
+ };
1090
+ }
1091
+
1092
+ /**
1093
+ * Get standard database template
1094
+ * @returns {Object} Database template
1095
+ */
1096
+ getStandardDatabaseTemplate() {
1097
+ return {
1098
+ version: '1.0.0',
1099
+ name: 'database-standard',
1100
+ description: 'Standard database configuration template',
1101
+ structure: {
1102
+ databases: [
1103
+ {
1104
+ binding: 'DB',
1105
+ database_name: '{{domainKey}}-auth-db',
1106
+ database_id: null
1107
+ }
1108
+ ],
1109
+
1110
+ migrations_table: 'migrations',
1111
+ backup_schedule: 'daily',
1112
+ retention_days: 30
1113
+ }
1114
+ };
1115
+ }
1116
+
1117
+ /**
1118
+ * Fetch Cloudflare accounts
1119
+ * @param {string} token - Cloudflare API token
1120
+ * @returns {Promise<Object>} Account information
1121
+ */
1122
+ async fetchCloudflareAccounts(token) {
1123
+ // Implementation would make actual API call
1124
+ // This is a placeholder for the real implementation
1125
+ return {
1126
+ id: 'account-id',
1127
+ name: 'Account Name'
1128
+ };
1129
+ }
1130
+
1131
+ /**
1132
+ * Fetch Cloudflare zone information
1133
+ * @param {string} domain - Domain name
1134
+ * @param {string} token - Cloudflare API token
1135
+ * @returns {Promise<Object>} Zone information
1136
+ */
1137
+ async fetchCloudflareZone(domain, token) {
1138
+ // Implementation would make actual API call
1139
+ // This is a placeholder for the real implementation
1140
+ return {
1141
+ id: 'zone-id',
1142
+ name: domain
1143
+ };
1144
+ }
1145
+
1146
+ /**
1147
+ * Generate standard configuration structure
1148
+ * @param {string} domain - Domain name
1149
+ * @param {Object} accountInfo - Account information
1150
+ * @param {Object} zoneInfo - Zone information
1151
+ * @returns {Object} Standard configuration
1152
+ */
1153
+ generateStandardConfig(domain, accountInfo, zoneInfo) {
1154
+ const domainKey = this.getDomainKey(domain);
1155
+ const displayName = this.getDisplayName(domain);
1156
+
1157
+ return {
1158
+ name: domainKey,
1159
+ displayName,
1160
+
1161
+ domains: {
1162
+ production: domain,
1163
+ staging: domain
1164
+ },
1165
+
1166
+ services: {
1167
+ frontend: {
1168
+ production: `https://${domain}`,
1169
+ staging: `https://${domain}`
1170
+ },
1171
+ api: {
1172
+ production: `https://${domainKey}-data-service.tamylatrading.workers.dev`,
1173
+ staging: `https://${domainKey}-data-service-staging.tamylatrading.workers.dev`
1174
+ }
1175
+ },
1176
+
1177
+ corsOrigins: {
1178
+ production: [`https://${domain}`, `https://*.${domain}`],
1179
+ staging: [`https://${domain}`, `https://*.${domain}`]
1180
+ },
1181
+
1182
+ databases: {
1183
+ production: { name: `${domainKey}-auth-db`, id: null },
1184
+ staging: { name: `${domainKey}-auth-db-staging`, id: null }
1185
+ }
1186
+ };
1187
+ }
1188
+ }
1189
+
1190
+ // Legacy function exports for backward compatibility
1191
+
1192
+ /**
1193
+ * Simple configuration cache
1194
+ * @param {string} domain - Domain name
1195
+ * @param {Object} options - Cache options
1196
+ * @returns {ConfigurationCacheManager} Cache manager instance
1197
+ */
1198
+ export function createConfigCache(domain, options = {}) {
1199
+ const cache = new ConfigurationCacheManager(options);
1200
+
1201
+ return {
1202
+ get: (env) => cache.getOrCreateDomainConfig(domain, { environment: env }),
1203
+ invalidate: (env) => cache.invalidateCache(domain, env),
1204
+ save: (config, env) => cache.saveConfiguration(domain, config, env),
1205
+ validate: (config) => cache.validateConfiguration(config)
1206
+ };
1207
+ }
1208
+
1209
+ /**
1210
+ * Generate configuration from template
1211
+ * @param {string} domain - Domain name
1212
+ * @param {string} templateName - Template name
1213
+ * @param {Object} values - Template values
1214
+ * @returns {Promise<Object>} Generated configuration
1215
+ */
1216
+ export async function generateConfig(domain, templateName = 'domain-standard', values = {}) {
1217
+ const cache = new ConfigurationCacheManager();
1218
+ return cache.generateFromTemplate(domain, { templateName, customValues: values });
1219
+ }
1220
+
1221
+ /**
1222
+ * Discover domain configuration
1223
+ * @param {string} domain - Domain name
1224
+ * @param {string} cloudflareToken - Cloudflare API token
1225
+ * @returns {Promise<Object>} Discovered configuration
1226
+ */
1227
+ export async function discoverConfig(domain, cloudflareToken) {
1228
+ const cache = new ConfigurationCacheManager({ enableRuntimeDiscovery: true });
1229
+ return cache.discoverDomainConfiguration(domain, { cloudflareToken });
1230
+ }