@tamyla/clodo-framework 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/CHANGELOG.md +564 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1393 -0
  4. package/bin/README.md +71 -0
  5. package/bin/clodo-service.js +416 -0
  6. package/bin/security/security-cli.js +96 -0
  7. package/bin/service-management/README.md +74 -0
  8. package/bin/service-management/create-service.js +129 -0
  9. package/bin/service-management/init-service.js +102 -0
  10. package/bin/service-management/init-service.js.backup +889 -0
  11. package/bin/shared/config/customer-cli.js +293 -0
  12. package/dist/config/ConfigurationManager.js +159 -0
  13. package/dist/config/CustomerConfigCLI.js +220 -0
  14. package/dist/config/FeatureManager.js +426 -0
  15. package/dist/config/customers.js +441 -0
  16. package/dist/config/domains.js +180 -0
  17. package/dist/config/features.js +225 -0
  18. package/dist/config/index.js +6 -0
  19. package/dist/database/database-orchestrator.js +730 -0
  20. package/dist/database/index.js +4 -0
  21. package/dist/deployment/auditor.js +971 -0
  22. package/dist/deployment/index.js +10 -0
  23. package/dist/deployment/rollback-manager.js +523 -0
  24. package/dist/deployment/testers/api-tester.js +80 -0
  25. package/dist/deployment/testers/auth-tester.js +129 -0
  26. package/dist/deployment/testers/core.js +217 -0
  27. package/dist/deployment/testers/database-tester.js +105 -0
  28. package/dist/deployment/testers/index.js +74 -0
  29. package/dist/deployment/testers/load-tester.js +120 -0
  30. package/dist/deployment/testers/performance-tester.js +105 -0
  31. package/dist/deployment/validator.js +558 -0
  32. package/dist/deployment/wrangler-deployer.js +574 -0
  33. package/dist/handlers/GenericRouteHandler.js +532 -0
  34. package/dist/index.js +39 -0
  35. package/dist/migration/MigrationAdapters.js +562 -0
  36. package/dist/modules/ModuleManager.js +668 -0
  37. package/dist/modules/security.js +98 -0
  38. package/dist/orchestration/cross-domain-coordinator.js +1083 -0
  39. package/dist/orchestration/index.js +5 -0
  40. package/dist/orchestration/modules/DeploymentCoordinator.js +258 -0
  41. package/dist/orchestration/modules/DomainResolver.js +196 -0
  42. package/dist/orchestration/modules/StateManager.js +332 -0
  43. package/dist/orchestration/multi-domain-orchestrator.js +255 -0
  44. package/dist/routing/EnhancedRouter.js +158 -0
  45. package/dist/schema/SchemaManager.js +778 -0
  46. package/dist/security/ConfigurationValidator.js +490 -0
  47. package/dist/security/DeploymentManager.js +208 -0
  48. package/dist/security/SecretGenerator.js +142 -0
  49. package/dist/security/SecurityCLI.js +228 -0
  50. package/dist/security/index.js +51 -0
  51. package/dist/security/patterns/environment-rules.js +66 -0
  52. package/dist/security/patterns/insecure-patterns.js +21 -0
  53. package/dist/service-management/ConfirmationEngine.js +411 -0
  54. package/dist/service-management/ErrorTracker.js +294 -0
  55. package/dist/service-management/GenerationEngine.js +3109 -0
  56. package/dist/service-management/InputCollector.js +237 -0
  57. package/dist/service-management/ServiceCreator.js +229 -0
  58. package/dist/service-management/ServiceInitializer.js +448 -0
  59. package/dist/service-management/ServiceOrchestrator.js +638 -0
  60. package/dist/service-management/handlers/ConfigMutator.js +130 -0
  61. package/dist/service-management/handlers/ConfirmationHandler.js +71 -0
  62. package/dist/service-management/handlers/GenerationHandler.js +80 -0
  63. package/dist/service-management/handlers/InputHandler.js +59 -0
  64. package/dist/service-management/handlers/ValidationHandler.js +203 -0
  65. package/dist/service-management/index.js +7 -0
  66. package/dist/services/GenericDataService.js +488 -0
  67. package/dist/shared/cloudflare/domain-discovery.js +562 -0
  68. package/dist/shared/cloudflare/domain-manager.js +912 -0
  69. package/dist/shared/cloudflare/index.js +8 -0
  70. package/dist/shared/cloudflare/ops.js +387 -0
  71. package/dist/shared/config/cache.js +1167 -0
  72. package/dist/shared/config/command-config-manager.js +174 -0
  73. package/dist/shared/config/customer-cli.js +258 -0
  74. package/dist/shared/config/index.js +9 -0
  75. package/dist/shared/config/manager.js +289 -0
  76. package/dist/shared/database/connection-manager.js +338 -0
  77. package/dist/shared/database/index.js +7 -0
  78. package/dist/shared/database/orchestrator.js +632 -0
  79. package/dist/shared/deployment/auditor.js +971 -0
  80. package/dist/shared/deployment/index.js +10 -0
  81. package/dist/shared/deployment/rollback-manager.js +523 -0
  82. package/dist/shared/deployment/validator.js +558 -0
  83. package/dist/shared/index.js +32 -0
  84. package/dist/shared/monitoring/health-checker.js +250 -0
  85. package/dist/shared/monitoring/index.js +8 -0
  86. package/dist/shared/monitoring/memory-manager.js +382 -0
  87. package/dist/shared/monitoring/production-monitor.js +390 -0
  88. package/dist/shared/production-tester/api-tester.js +80 -0
  89. package/dist/shared/production-tester/auth-tester.js +129 -0
  90. package/dist/shared/production-tester/core.js +217 -0
  91. package/dist/shared/production-tester/database-tester.js +105 -0
  92. package/dist/shared/production-tester/index.js +74 -0
  93. package/dist/shared/production-tester/load-tester.js +120 -0
  94. package/dist/shared/production-tester/performance-tester.js +105 -0
  95. package/dist/shared/security/api-token-manager.js +296 -0
  96. package/dist/shared/security/index.js +8 -0
  97. package/dist/shared/security/secret-generator.js +918 -0
  98. package/dist/shared/security/secure-token-manager.js +379 -0
  99. package/dist/shared/utils/error-recovery.js +240 -0
  100. package/dist/shared/utils/graceful-shutdown-manager.js +380 -0
  101. package/dist/shared/utils/index.js +9 -0
  102. package/dist/shared/utils/interactive-prompts.js +134 -0
  103. package/dist/shared/utils/rate-limiter.js +249 -0
  104. package/dist/utils/ErrorHandler.js +173 -0
  105. package/dist/utils/deployment/config-cache.js +1160 -0
  106. package/dist/utils/deployment/index.js +6 -0
  107. package/dist/utils/deployment/interactive-prompts.js +97 -0
  108. package/dist/utils/deployment/secret-generator.js +896 -0
  109. package/dist/utils/dirname-helper.js +35 -0
  110. package/dist/utils/domain-config.js +159 -0
  111. package/dist/utils/error-recovery.js +240 -0
  112. package/dist/utils/esm-helper.js +52 -0
  113. package/dist/utils/framework-config.js +481 -0
  114. package/dist/utils/graceful-shutdown-manager.js +379 -0
  115. package/dist/utils/health-checker.js +114 -0
  116. package/dist/utils/index.js +36 -0
  117. package/dist/utils/prompt-handler.js +98 -0
  118. package/dist/utils/usage-tracker.js +252 -0
  119. package/dist/utils/validation.js +112 -0
  120. package/dist/version/VersionDetector.js +723 -0
  121. package/dist/worker/index.js +4 -0
  122. package/dist/worker/integration.js +332 -0
  123. package/docs/FRAMEWORK-ARCHITECTURE-OVERVIEW.md +206 -0
  124. package/docs/INTEGRATION_GUIDE.md +2045 -0
  125. package/docs/README.md +82 -0
  126. package/docs/SECURITY.md +242 -0
  127. package/docs/deployment/deployment-guide.md +540 -0
  128. package/docs/overview.md +280 -0
  129. package/package.json +176 -0
  130. package/types/index.d.ts +575 -0
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Configuration Manager Module
3
+ * Configuration file management and validation
4
+ *
5
+ * Consolidates config management across 34+ scripts
6
+ */
7
+
8
+ import { readFileSync, writeFileSync, existsSync, copyFileSync, readdirSync, statSync } from 'fs';
9
+ import { join } from 'path';
10
+ export function updateWranglerConfig(updates, configPath = 'wrangler.toml') {
11
+ if (!existsSync(configPath)) {
12
+ throw new Error(`Configuration file not found: ${configPath}`);
13
+ }
14
+ let config = readFileSync(configPath, 'utf8');
15
+ let changesMade = [];
16
+
17
+ // Apply updates systematically
18
+ for (const [key, value] of Object.entries(updates)) {
19
+ switch (key) {
20
+ case 'workerName':
21
+ // Update main worker name
22
+ if (config.match(/^name = "[^"]*"/m)) {
23
+ config = config.replace(/^name = "[^"]*"/m, `name = "${value}"`);
24
+ changesMade.push(`Updated main worker name to: ${value}`);
25
+ }
26
+
27
+ // Update production environment name
28
+ if (config.match(/^\[env\.production\]\s*\nname = "[^"]*"/m)) {
29
+ config = config.replace(/^\[env\.production\]\s*\nname = "[^"]*"/m, `[env.production]\nname = "${value}"`);
30
+ changesMade.push(`Updated production worker name to: ${value}`);
31
+ }
32
+ break;
33
+ case 'databaseName':
34
+ {
35
+ const dbNameRegex = /database_name = "[^"]*"/g;
36
+ if (config.match(dbNameRegex)) {
37
+ config = config.replace(dbNameRegex, `database_name = "${value}"`);
38
+ changesMade.push(`Updated database name to: ${value}`);
39
+ }
40
+ break;
41
+ }
42
+ case 'databaseId':
43
+ {
44
+ const dbIdRegex = /database_id = "[^"]*"/g;
45
+ if (config.match(dbIdRegex)) {
46
+ config = config.replace(dbIdRegex, `database_id = "${value}"`);
47
+ changesMade.push(`Updated database ID to: ${value}`);
48
+ }
49
+ break;
50
+ }
51
+ case 'serviceDomain':
52
+ {
53
+ const domainRegex = /SERVICE_DOMAIN = "[^"]*"/g;
54
+ if (config.match(domainRegex)) {
55
+ config = config.replace(domainRegex, `SERVICE_DOMAIN = "${value}"`);
56
+ changesMade.push(`Updated service domain to: ${value}`);
57
+ }
58
+ break;
59
+ }
60
+ case 'compatibilityDate':
61
+ {
62
+ const dateRegex = /^compatibility_date = "[^"]*"/m;
63
+ if (config.match(dateRegex)) {
64
+ config = config.replace(dateRegex, `compatibility_date = "${value}"`);
65
+ changesMade.push(`Updated compatibility date to: ${value}`);
66
+ }
67
+ break;
68
+ }
69
+ default:
70
+ console.warn(`Unknown config update key: ${key}`);
71
+ }
72
+ }
73
+ writeFileSync(configPath, config);
74
+ return {
75
+ configPath,
76
+ changesMade,
77
+ success: changesMade.length > 0
78
+ };
79
+ }
80
+ export function backupConfig(configPath = 'wrangler.toml', suffix = null) {
81
+ if (!existsSync(configPath)) {
82
+ throw new Error(`Configuration file not found: ${configPath}`);
83
+ }
84
+ const timestamp = suffix || new Date().toISOString().replace(/[:.]/g, '-');
85
+ const backupPath = `${configPath}.backup.${timestamp}`;
86
+ copyFileSync(configPath, backupPath);
87
+ return {
88
+ originalPath: configPath,
89
+ backupPath,
90
+ timestamp: new Date().toISOString()
91
+ };
92
+ }
93
+ export function validateConfig(configPath = 'wrangler.toml') {
94
+ if (!existsSync(configPath)) {
95
+ throw new Error(`Configuration file not found: ${configPath}`);
96
+ }
97
+ const config = readFileSync(configPath, 'utf8');
98
+ const issues = [];
99
+ const warnings = [];
100
+
101
+ // Required fields validation
102
+ const requiredFields = [{
103
+ pattern: /^name = "/m,
104
+ description: 'Worker name'
105
+ }, {
106
+ pattern: /^compatibility_date = "/m,
107
+ description: 'Compatibility date'
108
+ }, {
109
+ pattern: /^main = "/m,
110
+ description: 'Main entry point'
111
+ }];
112
+ requiredFields.forEach(field => {
113
+ if (!config.match(field.pattern)) {
114
+ issues.push(`Missing required field: ${field.description}`);
115
+ }
116
+ });
117
+
118
+ // Database configuration validation
119
+ const hasDatabaseBinding = config.includes('[[d1_databases]]');
120
+ if (hasDatabaseBinding) {
121
+ if (!config.includes('database_name =')) {
122
+ issues.push('Database binding found but missing database_name');
123
+ }
124
+ if (!config.includes('database_id =')) {
125
+ issues.push('Database binding found but missing database_id');
126
+ }
127
+ }
128
+
129
+ // Environment validation
130
+ const hasProductionEnv = config.includes('[env.production]');
131
+ if (hasProductionEnv) {
132
+ const prodSection = config.split('[env.production]')[1];
133
+ if (prodSection && !prodSection.includes('name =')) {
134
+ warnings.push('Production environment missing explicit name');
135
+ }
136
+ }
137
+
138
+ // Compatibility date validation
139
+ const compatMatch = config.match(/compatibility_date = "([^"]+)"/);
140
+ if (compatMatch) {
141
+ const date = new Date(compatMatch[1]);
142
+ const now = new Date();
143
+ const daysDiff = (now - date) / (1000 * 60 * 60 * 24);
144
+ if (daysDiff > 365) {
145
+ warnings.push(`Compatibility date is ${Math.floor(daysDiff)} days old`);
146
+ }
147
+ }
148
+ return {
149
+ isValid: issues.length === 0,
150
+ issues,
151
+ warnings,
152
+ summary: {
153
+ errors: issues.length,
154
+ warnings: warnings.length,
155
+ status: issues.length === 0 ? 'valid' : 'invalid'
156
+ }
157
+ };
158
+ }
159
+ export function parseWranglerConfig(configPath = 'wrangler.toml') {
160
+ if (!existsSync(configPath)) {
161
+ throw new Error(`Configuration file not found: ${configPath}`);
162
+ }
163
+ const config = readFileSync(configPath, 'utf8');
164
+ const parsed = {};
165
+
166
+ // Extract main fields
167
+ const extractField = (pattern, key) => {
168
+ const match = config.match(pattern);
169
+ if (match) parsed[key] = match[1];
170
+ };
171
+ extractField(/^name = "([^"]+)"/m, 'name');
172
+ extractField(/^compatibility_date = "([^"]+)"/m, 'compatibilityDate');
173
+ extractField(/^main = "([^"]+)"/m, 'main');
174
+
175
+ // Extract database configuration
176
+ if (config.includes('[[d1_databases]]')) {
177
+ parsed.database = {};
178
+ extractField(/database_name = "([^"]+)"/m, 'name');
179
+ extractField(/database_id = "([^"]+)"/m, 'id');
180
+ if (parsed.name) parsed.database.name = parsed.name;
181
+ if (parsed.id) parsed.database.id = parsed.id;
182
+ delete parsed.name;
183
+ delete parsed.id;
184
+ }
185
+
186
+ // Extract environment configurations
187
+ parsed.environments = {};
188
+ const envMatches = config.matchAll(/\[env\.([^\]]+)\]/g);
189
+ for (const match of envMatches) {
190
+ const envName = match[1];
191
+ parsed.environments[envName] = {};
192
+
193
+ // Extract env-specific settings (simplified parsing)
194
+ const envSection = config.split(`[env.${envName}]`)[1];
195
+ if (envSection) {
196
+ const nextEnv = envSection.indexOf('[env.');
197
+ const sectionContent = nextEnv > 0 ? envSection.substring(0, nextEnv) : envSection;
198
+ const nameMatch = sectionContent.match(/name = "([^"]+)"/);
199
+ if (nameMatch) parsed.environments[envName].name = nameMatch[1];
200
+ }
201
+ }
202
+ return parsed;
203
+ }
204
+ export function generateWranglerConfig(options) {
205
+ const {
206
+ name,
207
+ main = 'src/worker/index.js',
208
+ compatibilityDate = new Date().toISOString().split('T')[0],
209
+ database = null,
210
+ environments = {},
211
+ vars = {},
212
+ secrets = []
213
+ } = options;
214
+ let config = `# Generated Wrangler Configuration
215
+ # Timestamp: ${new Date().toISOString()}
216
+
217
+ name = "${name}"
218
+ main = "${main}"
219
+ compatibility_date = "${compatibilityDate}"
220
+ `;
221
+
222
+ // Add variables
223
+ if (Object.keys(vars).length > 0) {
224
+ config += '\n[vars]\n';
225
+ Object.entries(vars).forEach(([key, value]) => {
226
+ config += `${key} = "${value}"\n`;
227
+ });
228
+ }
229
+
230
+ // Add database configuration
231
+ if (database) {
232
+ config += `
233
+ [[d1_databases]]
234
+ binding = "DB"
235
+ database_name = "${database.name}"
236
+ database_id = "${database.id}"
237
+ `;
238
+ }
239
+
240
+ // Add environments
241
+ Object.entries(environments).forEach(([envName, envConfig]) => {
242
+ config += `\n[env.${envName}]\n`;
243
+ if (envConfig.name) config += `name = "${envConfig.name}"\n`;
244
+ if (envConfig.vars) {
245
+ Object.entries(envConfig.vars).forEach(([key, value]) => {
246
+ config += `${key} = "${value}"\n`;
247
+ });
248
+ }
249
+ if (envConfig.database) {
250
+ config += `
251
+ [[env.${envName}.d1_databases]]
252
+ binding = "DB"
253
+ database_name = "${envConfig.database.name}"
254
+ database_id = "${envConfig.database.id}"
255
+ `;
256
+ }
257
+ });
258
+ return config;
259
+ }
260
+ export function listConfigFiles(directory = '.') {
261
+ const configFiles = [];
262
+ const patterns = ['wrangler.toml', 'package.json', '.env*'];
263
+ try {
264
+ const items = readdirSync(directory);
265
+ items.forEach(item => {
266
+ const path = join(directory, item);
267
+ const stat = statSync(path);
268
+ if (stat.isFile()) {
269
+ const matches = patterns.some(pattern => {
270
+ if (pattern.includes('*')) {
271
+ return item.startsWith(pattern.replace('*', ''));
272
+ }
273
+ return item === pattern;
274
+ });
275
+ if (matches) {
276
+ configFiles.push({
277
+ name: item,
278
+ path,
279
+ size: stat.size,
280
+ modified: stat.mtime
281
+ });
282
+ }
283
+ }
284
+ });
285
+ return configFiles.sort((a, b) => a.name.localeCompare(b.name));
286
+ } catch (error) {
287
+ return [];
288
+ }
289
+ }
@@ -0,0 +1,338 @@
1
+ /**
2
+ * Database Connection Manager
3
+ * Implements connection pooling, timeout handling, and retry logic for D1 operations
4
+ */
5
+
6
+ import { executeSql } from '../cloudflare/ops.js';
7
+ import { ErrorRecoveryManager } from '../utils/error-recovery.js';
8
+ export class DatabaseConnectionManager {
9
+ constructor(options = {}) {
10
+ this.options = options;
11
+ this.config = null;
12
+ this.connectionPool = new Map();
13
+ this.activeConnections = 0;
14
+ this.errorRecovery = null; // Will be initialized after framework config is loaded
15
+ }
16
+
17
+ /**
18
+ * Initialize with framework configuration
19
+ */
20
+ async initialize() {
21
+ // Import framework config for consistent database connection settings
22
+ const {
23
+ frameworkConfig
24
+ } = await import('../../../src/utils/framework-config.js');
25
+ const timing = frameworkConfig.getTiming();
26
+ const database = frameworkConfig.getDatabaseConfig();
27
+ this.config = {
28
+ maxRetries: this.options.maxRetries || timing.retryAttempts,
29
+ retryDelay: this.options.retryDelay || timing.retryDelay,
30
+ connectionTimeout: this.options.connectionTimeout || database.connectionTimeout,
31
+ queryTimeout: this.options.queryTimeout || database.queryTimeout,
32
+ enableConnectionPooling: this.options.enableConnectionPooling !== false,
33
+ maxPoolSize: this.options.maxPoolSize || database.maxPoolSize,
34
+ connectionIdleTimeout: this.options.connectionIdleTimeout || database.connectionIdleTimeout,
35
+ ...this.options
36
+ };
37
+
38
+ // Initialize error recovery with loaded config
39
+ const {
40
+ ErrorRecoveryManager
41
+ } = await import('../utils/error-recovery.js');
42
+ this.errorRecovery = new ErrorRecoveryManager({
43
+ maxRetries: this.config.maxRetries,
44
+ retryDelay: this.config.retryDelay,
45
+ gracefulDegradation: true
46
+ });
47
+ await this.errorRecovery.initialize();
48
+ }
49
+
50
+ /**
51
+ * Execute a database query with connection management
52
+ */
53
+ async executeQuery(databaseName, sql, options = {}) {
54
+ if (!this.config) {
55
+ throw new Error('ConnectionManager must be initialized before use. Call initialize() first.');
56
+ }
57
+ const config = {
58
+ env: 'production',
59
+ timeout: this.config.queryTimeout,
60
+ usePool: this.config.enableConnectionPooling,
61
+ ...options
62
+ };
63
+ return await this.errorRecovery.executeWithRecovery(async () => {
64
+ return await this.executeQueryInternal(databaseName, sql, config);
65
+ }, {
66
+ operationId: `db_query_${databaseName}`
67
+ });
68
+ }
69
+
70
+ /**
71
+ * Execute query with timeout and connection handling
72
+ */
73
+ async executeQueryInternal(databaseName, sql, config) {
74
+ const startTime = Date.now();
75
+ try {
76
+ // Get or create connection
77
+ const connection = await this.getConnection(databaseName, config);
78
+
79
+ // Execute query with timeout
80
+ const result = await this.executeWithTimeout(() => executeSql(databaseName, sql, config.env), config.timeout);
81
+ const duration = Date.now() - startTime;
82
+
83
+ // Release connection back to pool
84
+ if (config.usePool) {
85
+ this.releaseConnection(databaseName, connection);
86
+ }
87
+ return {
88
+ success: true,
89
+ result,
90
+ duration,
91
+ connectionId: connection.id
92
+ };
93
+ } catch (error) {
94
+ const duration = Date.now() - startTime;
95
+
96
+ // Handle connection errors
97
+ if (this.isConnectionError(error)) {
98
+ await this.handleConnectionError(databaseName, error);
99
+ }
100
+ throw new Error(`Database query failed: ${error.message} (duration: ${duration}ms)`);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Execute multiple queries in a transaction
106
+ */
107
+ async executeTransaction(databaseName, queries, options = {}) {
108
+ const config = {
109
+ env: 'production',
110
+ timeout: this.config.queryTimeout * queries.length,
111
+ // Longer timeout for transactions
112
+ ...options
113
+ };
114
+ return await this.errorRecovery.executeWithRecovery(async () => {
115
+ return await this.executeTransactionInternal(databaseName, queries, config);
116
+ }, {
117
+ operationId: `db_transaction_${databaseName}`
118
+ });
119
+ }
120
+
121
+ /**
122
+ * Execute transaction with proper error handling
123
+ */
124
+ async executeTransactionInternal(databaseName, queries, config) {
125
+ const startTime = Date.now();
126
+ const results = [];
127
+ try {
128
+ const connection = await this.getConnection(databaseName, config);
129
+ for (let i = 0; i < queries.length; i++) {
130
+ const query = queries[i];
131
+ try {
132
+ const result = await this.executeWithTimeout(() => executeSql(databaseName, query, config.env), config.timeout / queries.length // Divide timeout among queries
133
+ );
134
+ results.push({
135
+ index: i,
136
+ success: true,
137
+ result,
138
+ query
139
+ });
140
+ } catch (queryError) {
141
+ // Transaction failed, all previous queries should be rolled back
142
+ // Note: D1 doesn't support explicit transactions, so we need to handle this at application level
143
+ results.push({
144
+ index: i,
145
+ success: false,
146
+ error: queryError.message,
147
+ query
148
+ });
149
+ throw new Error(`Transaction failed at query ${i}: ${queryError.message}`);
150
+ }
151
+ }
152
+ const duration = Date.now() - startTime;
153
+ if (config.usePool) {
154
+ this.releaseConnection(databaseName, connection);
155
+ }
156
+ return {
157
+ success: true,
158
+ results,
159
+ duration,
160
+ transactionId: `txn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
161
+ };
162
+ } catch (error) {
163
+ const duration = Date.now() - startTime;
164
+ throw new Error(`Database transaction failed: ${error.message} (duration: ${duration}ms)`);
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Get a connection from the pool or create a new one
170
+ */
171
+ async getConnection(databaseName, config) {
172
+ if (!config.usePool) {
173
+ return this.createConnection(databaseName);
174
+ }
175
+ const poolKey = databaseName;
176
+ let pool = this.connectionPool.get(poolKey);
177
+ if (!pool) {
178
+ pool = [];
179
+ this.connectionPool.set(poolKey, pool);
180
+ }
181
+
182
+ // Find an available connection
183
+ let connection = pool.find(conn => !conn.inUse && !this.isConnectionExpired(conn));
184
+ if (!connection && pool.length < this.config.maxPoolSize) {
185
+ connection = this.createConnection(databaseName);
186
+ pool.push(connection);
187
+ }
188
+ if (!connection) {
189
+ // Wait for an available connection
190
+ connection = await this.waitForAvailableConnection(pool, config.timeout);
191
+ }
192
+ if (!connection) {
193
+ throw new Error('No available database connections');
194
+ }
195
+ connection.inUse = true;
196
+ connection.lastUsed = Date.now();
197
+ this.activeConnections++;
198
+ return connection;
199
+ }
200
+
201
+ /**
202
+ * Release a connection back to the pool
203
+ */
204
+ releaseConnection(databaseName, connection) {
205
+ connection.inUse = false;
206
+ connection.lastUsed = Date.now();
207
+ this.activeConnections--;
208
+ }
209
+
210
+ /**
211
+ * Create a new database connection
212
+ */
213
+ createConnection(databaseName) {
214
+ return {
215
+ id: `conn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
216
+ databaseName,
217
+ created: Date.now(),
218
+ lastUsed: Date.now(),
219
+ inUse: false
220
+ };
221
+ }
222
+
223
+ /**
224
+ * Wait for an available connection
225
+ */
226
+ async waitForAvailableConnection(pool, timeout) {
227
+ const startTime = Date.now();
228
+ while (Date.now() - startTime < timeout) {
229
+ const connection = pool.find(conn => !conn.inUse && !this.isConnectionExpired(conn));
230
+ if (connection) {
231
+ return connection;
232
+ }
233
+
234
+ // Wait a bit before checking again
235
+ await new Promise(resolve => setTimeout(resolve, 100));
236
+ }
237
+ return null;
238
+ }
239
+
240
+ /**
241
+ * Check if a connection is expired
242
+ */
243
+ isConnectionExpired(connection) {
244
+ const now = Date.now();
245
+ return now - connection.lastUsed > this.config.connectionIdleTimeout;
246
+ }
247
+
248
+ /**
249
+ * Handle connection errors
250
+ */
251
+ async handleConnectionError(databaseName, error) {
252
+ // Clean up any bad connections from the pool
253
+ const poolKey = databaseName;
254
+ const pool = this.connectionPool.get(poolKey);
255
+ if (pool) {
256
+ const validConnections = pool.filter(conn => !this.isConnectionExpired(conn));
257
+ this.connectionPool.set(poolKey, validConnections);
258
+ }
259
+
260
+ // Could implement connection health checks here
261
+ console.warn(`Database connection error for ${databaseName}:`, error.message);
262
+ }
263
+
264
+ /**
265
+ * Check if error is connection-related
266
+ */
267
+ isConnectionError(error) {
268
+ const connectionErrorPatterns = ['connection', 'timeout', 'network', 'ECONNREFUSED', 'ENOTFOUND', 'ETIMEDOUT'];
269
+ const errorMessage = error.message.toLowerCase();
270
+ return connectionErrorPatterns.some(pattern => errorMessage.includes(pattern));
271
+ }
272
+
273
+ /**
274
+ * Execute a function with timeout
275
+ */
276
+ async executeWithTimeout(fn, timeout) {
277
+ return new Promise((resolve, reject) => {
278
+ const timeoutId = setTimeout(() => {
279
+ reject(new Error(`Operation timed out after ${timeout}ms`));
280
+ }, timeout);
281
+ fn().then(result => {
282
+ clearTimeout(timeoutId);
283
+ resolve(result);
284
+ }).catch(error => {
285
+ clearTimeout(timeoutId);
286
+ reject(error);
287
+ });
288
+ });
289
+ }
290
+
291
+ /**
292
+ * Get connection pool statistics
293
+ */
294
+ getPoolStats(databaseName) {
295
+ const pool = this.connectionPool.get(databaseName) || [];
296
+ return {
297
+ databaseName,
298
+ totalConnections: pool.length,
299
+ activeConnections: pool.filter(conn => conn.inUse).length,
300
+ idleConnections: pool.filter(conn => !conn.inUse).length,
301
+ expiredConnections: pool.filter(conn => this.isConnectionExpired(conn)).length,
302
+ globalActiveConnections: this.activeConnections
303
+ };
304
+ }
305
+
306
+ /**
307
+ * Get all pool statistics
308
+ */
309
+ getAllPoolStats() {
310
+ const stats = {};
311
+ for (const [databaseName, pool] of this.connectionPool) {
312
+ stats[databaseName] = this.getPoolStats(databaseName);
313
+ }
314
+ return stats;
315
+ }
316
+
317
+ /**
318
+ * Clean up expired connections
319
+ */
320
+ cleanupExpiredConnections() {
321
+ for (const [databaseName, pool] of this.connectionPool) {
322
+ const validConnections = pool.filter(conn => !this.isConnectionExpired(conn));
323
+ this.connectionPool.set(databaseName, validConnections);
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Close all connections
329
+ */
330
+ async closeAllConnections() {
331
+ for (const [databaseName, pool] of this.connectionPool) {
332
+ // In a real implementation, you'd close actual connections
333
+ pool.length = 0;
334
+ }
335
+ this.connectionPool.clear();
336
+ this.activeConnections = 0;
337
+ }
338
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Database Module
3
+ * Exports all database management utilities
4
+ */
5
+
6
+ export { DatabaseOrchestrator } from './orchestrator.js';
7
+ export { DatabaseConnectionManager } from './connection-manager.js';