@trentapps/manager-protocol 1.3.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 (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +639 -0
  3. package/dist/analyzers/ArchitectureDetector.d.ts +44 -0
  4. package/dist/analyzers/ArchitectureDetector.d.ts.map +1 -0
  5. package/dist/analyzers/ArchitectureDetector.js +218 -0
  6. package/dist/analyzers/ArchitectureDetector.js.map +1 -0
  7. package/dist/analyzers/CSSAnalyzer.d.ts +284 -0
  8. package/dist/analyzers/CSSAnalyzer.d.ts.map +1 -0
  9. package/dist/analyzers/CSSAnalyzer.js +1180 -0
  10. package/dist/analyzers/CSSAnalyzer.js.map +1 -0
  11. package/dist/analyzers/index.d.ts +5 -0
  12. package/dist/analyzers/index.d.ts.map +1 -0
  13. package/dist/analyzers/index.js +5 -0
  14. package/dist/analyzers/index.js.map +1 -0
  15. package/dist/cli.d.ts +8 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +174 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/design-system/index.d.ts +6 -0
  20. package/dist/design-system/index.d.ts.map +1 -0
  21. package/dist/design-system/index.js +6 -0
  22. package/dist/design-system/index.js.map +1 -0
  23. package/dist/design-system/tokens.d.ts +106 -0
  24. package/dist/design-system/tokens.d.ts.map +1 -0
  25. package/dist/design-system/tokens.js +554 -0
  26. package/dist/design-system/tokens.js.map +1 -0
  27. package/dist/engine/AuditLogger.d.ts +506 -0
  28. package/dist/engine/AuditLogger.d.ts.map +1 -0
  29. package/dist/engine/AuditLogger.js +1491 -0
  30. package/dist/engine/AuditLogger.js.map +1 -0
  31. package/dist/engine/GitHubApprovalManager.d.ts +123 -0
  32. package/dist/engine/GitHubApprovalManager.d.ts.map +1 -0
  33. package/dist/engine/GitHubApprovalManager.js +347 -0
  34. package/dist/engine/GitHubApprovalManager.js.map +1 -0
  35. package/dist/engine/GitHubClient.d.ts +183 -0
  36. package/dist/engine/GitHubClient.d.ts.map +1 -0
  37. package/dist/engine/GitHubClient.js +411 -0
  38. package/dist/engine/GitHubClient.js.map +1 -0
  39. package/dist/engine/RateLimiter.d.ts +81 -0
  40. package/dist/engine/RateLimiter.d.ts.map +1 -0
  41. package/dist/engine/RateLimiter.js +215 -0
  42. package/dist/engine/RateLimiter.js.map +1 -0
  43. package/dist/engine/RuleDependencyAnalyzer.d.ts +73 -0
  44. package/dist/engine/RuleDependencyAnalyzer.d.ts.map +1 -0
  45. package/dist/engine/RuleDependencyAnalyzer.js +475 -0
  46. package/dist/engine/RuleDependencyAnalyzer.js.map +1 -0
  47. package/dist/engine/RulesEngine.d.ts +176 -0
  48. package/dist/engine/RulesEngine.d.ts.map +1 -0
  49. package/dist/engine/RulesEngine.js +705 -0
  50. package/dist/engine/RulesEngine.js.map +1 -0
  51. package/dist/engine/TaskManager.d.ts +174 -0
  52. package/dist/engine/TaskManager.d.ts.map +1 -0
  53. package/dist/engine/TaskManager.js +663 -0
  54. package/dist/engine/TaskManager.js.map +1 -0
  55. package/dist/engine/index.d.ts +11 -0
  56. package/dist/engine/index.d.ts.map +1 -0
  57. package/dist/engine/index.js +13 -0
  58. package/dist/engine/index.js.map +1 -0
  59. package/dist/index.d.ts +21 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +29 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/rules/architecture.d.ts +9 -0
  64. package/dist/rules/architecture.d.ts.map +1 -0
  65. package/dist/rules/architecture.js +322 -0
  66. package/dist/rules/architecture.js.map +1 -0
  67. package/dist/rules/azure.d.ts +7 -0
  68. package/dist/rules/azure.d.ts.map +1 -0
  69. package/dist/rules/azure.js +136 -0
  70. package/dist/rules/azure.js.map +1 -0
  71. package/dist/rules/compliance.d.ts +9 -0
  72. package/dist/rules/compliance.d.ts.map +1 -0
  73. package/dist/rules/compliance.js +286 -0
  74. package/dist/rules/compliance.js.map +1 -0
  75. package/dist/rules/condition-optimizer.d.ts +151 -0
  76. package/dist/rules/condition-optimizer.d.ts.map +1 -0
  77. package/dist/rules/condition-optimizer.js +479 -0
  78. package/dist/rules/condition-optimizer.js.map +1 -0
  79. package/dist/rules/css.d.ts +10 -0
  80. package/dist/rules/css.d.ts.map +1 -0
  81. package/dist/rules/css.js +1777 -0
  82. package/dist/rules/css.js.map +1 -0
  83. package/dist/rules/field-standards.d.ts +1172 -0
  84. package/dist/rules/field-standards.d.ts.map +1 -0
  85. package/dist/rules/field-standards.js +908 -0
  86. package/dist/rules/field-standards.js.map +1 -0
  87. package/dist/rules/flask.d.ts +7 -0
  88. package/dist/rules/flask.d.ts.map +1 -0
  89. package/dist/rules/flask.js +142 -0
  90. package/dist/rules/flask.js.map +1 -0
  91. package/dist/rules/index.d.ts +827 -0
  92. package/dist/rules/index.d.ts.map +1 -0
  93. package/dist/rules/index.js +556 -0
  94. package/dist/rules/index.js.map +1 -0
  95. package/dist/rules/ml-ai.d.ts +7 -0
  96. package/dist/rules/ml-ai.d.ts.map +1 -0
  97. package/dist/rules/ml-ai.js +148 -0
  98. package/dist/rules/ml-ai.js.map +1 -0
  99. package/dist/rules/operational.d.ts +9 -0
  100. package/dist/rules/operational.d.ts.map +1 -0
  101. package/dist/rules/operational.js +318 -0
  102. package/dist/rules/operational.js.map +1 -0
  103. package/dist/rules/patterns.d.ts +568 -0
  104. package/dist/rules/patterns.d.ts.map +1 -0
  105. package/dist/rules/patterns.js +1359 -0
  106. package/dist/rules/patterns.js.map +1 -0
  107. package/dist/rules/security.d.ts +9 -0
  108. package/dist/rules/security.d.ts.map +1 -0
  109. package/dist/rules/security.js +848 -0
  110. package/dist/rules/security.js.map +1 -0
  111. package/dist/rules/shared-patterns.d.ts +268 -0
  112. package/dist/rules/shared-patterns.d.ts.map +1 -0
  113. package/dist/rules/shared-patterns.js +556 -0
  114. package/dist/rules/shared-patterns.js.map +1 -0
  115. package/dist/rules/storage.d.ts +13 -0
  116. package/dist/rules/storage.d.ts.map +1 -0
  117. package/dist/rules/storage.js +672 -0
  118. package/dist/rules/storage.js.map +1 -0
  119. package/dist/rules/stripe.d.ts +7 -0
  120. package/dist/rules/stripe.d.ts.map +1 -0
  121. package/dist/rules/stripe.js +133 -0
  122. package/dist/rules/stripe.js.map +1 -0
  123. package/dist/rules/testing.d.ts +7 -0
  124. package/dist/rules/testing.d.ts.map +1 -0
  125. package/dist/rules/testing.js +135 -0
  126. package/dist/rules/testing.js.map +1 -0
  127. package/dist/rules/ux.d.ts +9 -0
  128. package/dist/rules/ux.d.ts.map +1 -0
  129. package/dist/rules/ux.js +280 -0
  130. package/dist/rules/ux.js.map +1 -0
  131. package/dist/rules/websocket.d.ts +7 -0
  132. package/dist/rules/websocket.d.ts.map +1 -0
  133. package/dist/rules/websocket.js +128 -0
  134. package/dist/rules/websocket.js.map +1 -0
  135. package/dist/server.d.ts +43 -0
  136. package/dist/server.d.ts.map +1 -0
  137. package/dist/server.js +1967 -0
  138. package/dist/server.js.map +1 -0
  139. package/dist/supervisor/AgentSupervisor.d.ts +195 -0
  140. package/dist/supervisor/AgentSupervisor.d.ts.map +1 -0
  141. package/dist/supervisor/AgentSupervisor.js +569 -0
  142. package/dist/supervisor/AgentSupervisor.js.map +1 -0
  143. package/dist/supervisor/ManagedServerRegistry.d.ts +185 -0
  144. package/dist/supervisor/ManagedServerRegistry.d.ts.map +1 -0
  145. package/dist/supervisor/ManagedServerRegistry.js +729 -0
  146. package/dist/supervisor/ManagedServerRegistry.js.map +1 -0
  147. package/dist/supervisor/ProjectTracker.d.ts +210 -0
  148. package/dist/supervisor/ProjectTracker.d.ts.map +1 -0
  149. package/dist/supervisor/ProjectTracker.js +709 -0
  150. package/dist/supervisor/ProjectTracker.js.map +1 -0
  151. package/dist/supervisor/index.d.ts +6 -0
  152. package/dist/supervisor/index.d.ts.map +1 -0
  153. package/dist/supervisor/index.js +6 -0
  154. package/dist/supervisor/index.js.map +1 -0
  155. package/dist/testing/index.d.ts +11 -0
  156. package/dist/testing/index.d.ts.map +1 -0
  157. package/dist/testing/index.js +12 -0
  158. package/dist/testing/index.js.map +1 -0
  159. package/dist/testing/rule-tester.d.ts +217 -0
  160. package/dist/testing/rule-tester.d.ts.map +1 -0
  161. package/dist/testing/rule-tester.examples.d.ts +57 -0
  162. package/dist/testing/rule-tester.examples.d.ts.map +1 -0
  163. package/dist/testing/rule-tester.examples.js +375 -0
  164. package/dist/testing/rule-tester.examples.js.map +1 -0
  165. package/dist/testing/rule-tester.js +381 -0
  166. package/dist/testing/rule-tester.js.map +1 -0
  167. package/dist/testing/rule-validator.d.ts +141 -0
  168. package/dist/testing/rule-validator.d.ts.map +1 -0
  169. package/dist/testing/rule-validator.js +640 -0
  170. package/dist/testing/rule-validator.js.map +1 -0
  171. package/dist/types/index.d.ts +1282 -0
  172. package/dist/types/index.d.ts.map +1 -0
  173. package/dist/types/index.js +386 -0
  174. package/dist/types/index.js.map +1 -0
  175. package/dist/utils/errors.d.ts +86 -0
  176. package/dist/utils/errors.d.ts.map +1 -0
  177. package/dist/utils/errors.js +171 -0
  178. package/dist/utils/errors.js.map +1 -0
  179. package/dist/utils/index.d.ts +7 -0
  180. package/dist/utils/index.d.ts.map +1 -0
  181. package/dist/utils/index.js +7 -0
  182. package/dist/utils/index.js.map +1 -0
  183. package/dist/utils/rate-limiting.d.ts +268 -0
  184. package/dist/utils/rate-limiting.d.ts.map +1 -0
  185. package/dist/utils/rate-limiting.js +403 -0
  186. package/dist/utils/rate-limiting.js.map +1 -0
  187. package/dist/utils/shared.d.ts +306 -0
  188. package/dist/utils/shared.d.ts.map +1 -0
  189. package/dist/utils/shared.js +464 -0
  190. package/dist/utils/shared.js.map +1 -0
  191. package/dist/utils/shell.d.ts +22 -0
  192. package/dist/utils/shell.d.ts.map +1 -0
  193. package/dist/utils/shell.js +29 -0
  194. package/dist/utils/shell.js.map +1 -0
  195. package/package.json +67 -0
@@ -0,0 +1,729 @@
1
+ /**
2
+ * Managed Server Registry
3
+ *
4
+ * Allows registering servers (ip + sourceDir) and checking online status.
5
+ * Features:
6
+ * - Connection pooling for TCP checks (reuse connections, timeout limits)
7
+ * - Caching for health check results (short TTL for success, no cache for failures)
8
+ * - Rate limiting with exponential backoff for repeated failures
9
+ */
10
+ import { promises as fsp } from 'fs';
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+ import net from 'net';
14
+ import { v4 as uuidv4 } from 'uuid';
15
+ /**
16
+ * TCP Connection Pool
17
+ * Manages reusable TCP connections with automatic cleanup
18
+ */
19
+ class TCPConnectionPool {
20
+ pools = new Map();
21
+ config;
22
+ cleanupTimer = null;
23
+ constructor(config = {}) {
24
+ this.config = {
25
+ maxConnectionsPerEndpoint: config.maxConnectionsPerEndpoint ?? 5,
26
+ connectionIdleTimeoutMs: config.connectionIdleTimeoutMs ?? 30000,
27
+ maxConnectionAgeMs: config.maxConnectionAgeMs ?? 60000,
28
+ cleanupIntervalMs: config.cleanupIntervalMs ?? 10000
29
+ };
30
+ this.startCleanupTimer();
31
+ }
32
+ getKey(host, port) {
33
+ return `${host}:${port}`;
34
+ }
35
+ startCleanupTimer() {
36
+ if (this.cleanupTimer)
37
+ return;
38
+ this.cleanupTimer = setInterval(() => {
39
+ this.cleanupStaleConnections();
40
+ }, this.config.cleanupIntervalMs);
41
+ // Don't block process exit
42
+ this.cleanupTimer.unref();
43
+ }
44
+ /**
45
+ * Clean up stale and idle connections
46
+ */
47
+ cleanupStaleConnections() {
48
+ const now = Date.now();
49
+ let cleaned = 0;
50
+ for (const [key, connections] of this.pools.entries()) {
51
+ const validConnections = [];
52
+ for (const conn of connections) {
53
+ const age = now - conn.createdAt;
54
+ const idle = now - conn.lastUsedAt;
55
+ // Remove if too old, too idle, or in error state
56
+ if (age > this.config.maxConnectionAgeMs ||
57
+ idle > this.config.connectionIdleTimeoutMs ||
58
+ conn.socket.destroyed) {
59
+ this.destroyConnection(conn);
60
+ cleaned++;
61
+ }
62
+ else if (!conn.inUse) {
63
+ validConnections.push(conn);
64
+ }
65
+ else {
66
+ validConnections.push(conn);
67
+ }
68
+ }
69
+ if (validConnections.length === 0) {
70
+ this.pools.delete(key);
71
+ }
72
+ else {
73
+ this.pools.set(key, validConnections);
74
+ }
75
+ }
76
+ return cleaned;
77
+ }
78
+ destroyConnection(conn) {
79
+ try {
80
+ if (!conn.socket.destroyed) {
81
+ conn.socket.destroy();
82
+ }
83
+ }
84
+ catch {
85
+ // Ignore destruction errors
86
+ }
87
+ }
88
+ /**
89
+ * Get or create a connection to the specified endpoint
90
+ */
91
+ async getConnection(host, port, timeoutMs) {
92
+ const key = this.getKey(host, port);
93
+ const pool = this.pools.get(key) || [];
94
+ // Try to find an available connection
95
+ for (const conn of pool) {
96
+ if (!conn.inUse && !conn.socket.destroyed) {
97
+ conn.inUse = true;
98
+ conn.lastUsedAt = Date.now();
99
+ return { socket: conn.socket, reused: true };
100
+ }
101
+ }
102
+ // Create new connection if under limit
103
+ if (pool.length < this.config.maxConnectionsPerEndpoint) {
104
+ const socket = await this.createConnection(host, port, timeoutMs);
105
+ const conn = {
106
+ socket,
107
+ host,
108
+ port,
109
+ createdAt: Date.now(),
110
+ lastUsedAt: Date.now(),
111
+ inUse: true
112
+ };
113
+ pool.push(conn);
114
+ this.pools.set(key, pool);
115
+ return { socket, reused: false };
116
+ }
117
+ // All connections in use, create a temporary one (won't be pooled)
118
+ const socket = await this.createConnection(host, port, timeoutMs);
119
+ return { socket, reused: false };
120
+ }
121
+ createConnection(host, port, timeoutMs) {
122
+ return new Promise((resolve, reject) => {
123
+ const socket = new net.Socket();
124
+ let settled = false;
125
+ const settle = (error) => {
126
+ if (settled)
127
+ return;
128
+ settled = true;
129
+ if (error) {
130
+ try {
131
+ socket.destroy();
132
+ }
133
+ catch { /* noop */ }
134
+ reject(error);
135
+ }
136
+ else {
137
+ resolve(socket);
138
+ }
139
+ };
140
+ socket.setTimeout(timeoutMs);
141
+ socket.once('connect', () => settle());
142
+ socket.once('timeout', () => settle(new Error('Connection timeout')));
143
+ socket.once('error', (err) => settle(err));
144
+ try {
145
+ socket.connect(port, host);
146
+ }
147
+ catch (err) {
148
+ settle(err instanceof Error ? err : new Error(String(err)));
149
+ }
150
+ });
151
+ }
152
+ /**
153
+ * Release a connection back to the pool
154
+ */
155
+ releaseConnection(host, port, socket, keepAlive = true) {
156
+ const key = this.getKey(host, port);
157
+ const pool = this.pools.get(key);
158
+ if (!pool) {
159
+ // Connection wasn't pooled, destroy it
160
+ try {
161
+ socket.destroy();
162
+ }
163
+ catch { /* noop */ }
164
+ return;
165
+ }
166
+ const conn = pool.find(c => c.socket === socket);
167
+ if (conn) {
168
+ conn.inUse = false;
169
+ conn.lastUsedAt = Date.now();
170
+ if (!keepAlive || socket.destroyed) {
171
+ // Remove from pool and destroy
172
+ const idx = pool.indexOf(conn);
173
+ if (idx !== -1)
174
+ pool.splice(idx, 1);
175
+ this.destroyConnection(conn);
176
+ }
177
+ }
178
+ else {
179
+ // Not a pooled connection, destroy it
180
+ try {
181
+ socket.destroy();
182
+ }
183
+ catch { /* noop */ }
184
+ }
185
+ }
186
+ /**
187
+ * Get pool statistics
188
+ */
189
+ getStats() {
190
+ let total = 0;
191
+ let active = 0;
192
+ for (const pool of this.pools.values()) {
193
+ total += pool.length;
194
+ active += pool.filter(c => c.inUse).length;
195
+ }
196
+ return {
197
+ totalConnections: total,
198
+ activeConnections: active,
199
+ endpoints: this.pools.size
200
+ };
201
+ }
202
+ /**
203
+ * Destroy all connections and stop cleanup timer
204
+ */
205
+ destroy() {
206
+ if (this.cleanupTimer) {
207
+ clearInterval(this.cleanupTimer);
208
+ this.cleanupTimer = null;
209
+ }
210
+ for (const pool of this.pools.values()) {
211
+ for (const conn of pool) {
212
+ this.destroyConnection(conn);
213
+ }
214
+ }
215
+ this.pools.clear();
216
+ }
217
+ }
218
+ /**
219
+ * Health Check Cache
220
+ * Caches successful TCP check results with LRU eviction
221
+ */
222
+ class HealthCheckCache {
223
+ cache = new Map();
224
+ config;
225
+ constructor(config = {}) {
226
+ this.config = {
227
+ successTtlMs: config.successTtlMs ?? 5000,
228
+ cacheFailures: config.cacheFailures ?? false,
229
+ failureTtlMs: config.failureTtlMs ?? 1000,
230
+ maxEntries: config.maxEntries ?? 1000
231
+ };
232
+ }
233
+ getKey(host, port) {
234
+ return `${host}:${port}`;
235
+ }
236
+ /**
237
+ * Get cached result if valid
238
+ */
239
+ get(host, port) {
240
+ const key = this.getKey(host, port);
241
+ const entry = this.cache.get(key);
242
+ if (!entry)
243
+ return null;
244
+ const now = Date.now();
245
+ const age = now - entry.timestamp;
246
+ const ttl = entry.result ? this.config.successTtlMs : this.config.failureTtlMs;
247
+ if (age > ttl) {
248
+ this.cache.delete(key);
249
+ return null;
250
+ }
251
+ // Update access tracking for LRU
252
+ entry.accessCount++;
253
+ entry.lastAccessedAt = now;
254
+ return { result: entry.result, ageMs: age };
255
+ }
256
+ /**
257
+ * Store a result in cache
258
+ */
259
+ set(host, port, result) {
260
+ // Don't cache failures unless configured to
261
+ if (!result && !this.config.cacheFailures) {
262
+ return;
263
+ }
264
+ // Enforce max entries with LRU eviction
265
+ if (this.cache.size >= this.config.maxEntries) {
266
+ this.evictLRU();
267
+ }
268
+ const key = this.getKey(host, port);
269
+ const now = Date.now();
270
+ this.cache.set(key, {
271
+ result,
272
+ timestamp: now,
273
+ accessCount: 1,
274
+ lastAccessedAt: now
275
+ });
276
+ }
277
+ /**
278
+ * Evict least recently used entries
279
+ */
280
+ evictLRU() {
281
+ // Evict 10% of entries
282
+ const toEvict = Math.ceil(this.config.maxEntries * 0.1);
283
+ const entries = Array.from(this.cache.entries())
284
+ .sort((a, b) => a[1].lastAccessedAt - b[1].lastAccessedAt);
285
+ for (let i = 0; i < toEvict && i < entries.length; i++) {
286
+ this.cache.delete(entries[i][0]);
287
+ }
288
+ }
289
+ /**
290
+ * Invalidate cache for a specific endpoint
291
+ */
292
+ invalidate(host, port) {
293
+ return this.cache.delete(this.getKey(host, port));
294
+ }
295
+ /**
296
+ * Clear all cached entries
297
+ */
298
+ clear() {
299
+ this.cache.clear();
300
+ }
301
+ /**
302
+ * Get cache statistics
303
+ */
304
+ getStats() {
305
+ let totalAccesses = 0;
306
+ for (const entry of this.cache.values()) {
307
+ totalAccesses += entry.accessCount;
308
+ }
309
+ return {
310
+ entries: this.cache.size,
311
+ hitRate: this.cache.size > 0 ? totalAccesses / this.cache.size : undefined
312
+ };
313
+ }
314
+ }
315
+ /**
316
+ * Rate Limiter with Exponential Backoff
317
+ * Prevents hammering unhealthy servers
318
+ */
319
+ class TCPRateLimiter {
320
+ states = new Map();
321
+ config;
322
+ constructor(config = {}) {
323
+ this.config = {
324
+ initialDelayMs: config.initialDelayMs ?? 1000,
325
+ maxDelayMs: config.maxDelayMs ?? 60000,
326
+ backoffMultiplier: config.backoffMultiplier ?? 2,
327
+ failureThreshold: config.failureThreshold ?? 3,
328
+ failureWindowMs: config.failureWindowMs ?? 60000
329
+ };
330
+ }
331
+ getKey(host, port) {
332
+ return `${host}:${port}`;
333
+ }
334
+ /**
335
+ * Check if an endpoint is currently rate limited
336
+ * Returns remaining wait time in ms, or 0 if not limited
337
+ */
338
+ isRateLimited(host, port) {
339
+ const key = this.getKey(host, port);
340
+ const state = this.states.get(key);
341
+ if (!state)
342
+ return 0;
343
+ const now = Date.now();
344
+ // Reset if outside failure window
345
+ if (now - state.lastFailureAt > this.config.failureWindowMs) {
346
+ this.states.delete(key);
347
+ return 0;
348
+ }
349
+ // Check if still blocked
350
+ if (now < state.blockedUntil) {
351
+ return state.blockedUntil - now;
352
+ }
353
+ return 0;
354
+ }
355
+ /**
356
+ * Record a successful check - resets failure tracking
357
+ */
358
+ recordSuccess(host, port) {
359
+ const key = this.getKey(host, port);
360
+ this.states.delete(key);
361
+ }
362
+ /**
363
+ * Record a failed check - may trigger rate limiting
364
+ */
365
+ recordFailure(host, port) {
366
+ const key = this.getKey(host, port);
367
+ const now = Date.now();
368
+ let state = this.states.get(key);
369
+ if (!state) {
370
+ state = {
371
+ consecutiveFailures: 0,
372
+ lastFailureAt: now,
373
+ currentDelayMs: this.config.initialDelayMs,
374
+ blockedUntil: 0
375
+ };
376
+ this.states.set(key, state);
377
+ }
378
+ // Reset if outside failure window
379
+ if (now - state.lastFailureAt > this.config.failureWindowMs) {
380
+ state.consecutiveFailures = 0;
381
+ state.currentDelayMs = this.config.initialDelayMs;
382
+ }
383
+ state.consecutiveFailures++;
384
+ state.lastFailureAt = now;
385
+ // Apply rate limiting after threshold
386
+ if (state.consecutiveFailures >= this.config.failureThreshold) {
387
+ state.blockedUntil = now + state.currentDelayMs;
388
+ // Exponential backoff for next failure
389
+ state.currentDelayMs = Math.min(state.currentDelayMs * this.config.backoffMultiplier, this.config.maxDelayMs);
390
+ }
391
+ }
392
+ /**
393
+ * Get rate limit state for an endpoint
394
+ */
395
+ getState(host, port) {
396
+ return this.states.get(this.getKey(host, port));
397
+ }
398
+ /**
399
+ * Clear rate limiting for an endpoint
400
+ */
401
+ clear(host, port) {
402
+ return this.states.delete(this.getKey(host, port));
403
+ }
404
+ /**
405
+ * Clear all rate limiting states
406
+ */
407
+ clearAll() {
408
+ this.states.clear();
409
+ }
410
+ /**
411
+ * Get statistics
412
+ */
413
+ getStats() {
414
+ const now = Date.now();
415
+ let rateLimited = 0;
416
+ for (const state of this.states.values()) {
417
+ if (now < state.blockedUntil) {
418
+ rateLimited++;
419
+ }
420
+ }
421
+ return {
422
+ trackedEndpoints: this.states.size,
423
+ rateLimitedEndpoints: rateLimited
424
+ };
425
+ }
426
+ }
427
+ export class ManagedServerRegistry {
428
+ filePath;
429
+ servers = new Map();
430
+ loaded = false;
431
+ // Connection pooling, caching, and rate limiting
432
+ connectionPool;
433
+ healthCache;
434
+ rateLimiter;
435
+ enablePooling;
436
+ enableCaching;
437
+ enableRateLimiting;
438
+ // Statistics tracking
439
+ stats = {
440
+ totalChecks: 0,
441
+ cacheHits: 0,
442
+ cacheMisses: 0,
443
+ pooledConnections: 0,
444
+ newConnections: 0,
445
+ rateLimitedChecks: 0
446
+ };
447
+ constructor(config = {}) {
448
+ this.filePath = config.filePath || path.join(process.cwd(), 'managed-servers.json');
449
+ // Initialize components with provided config or defaults
450
+ this.connectionPool = new TCPConnectionPool(config.connectionPool);
451
+ this.healthCache = new HealthCheckCache(config.cache);
452
+ this.rateLimiter = new TCPRateLimiter(config.rateLimit);
453
+ // Feature flags
454
+ this.enablePooling = config.enablePooling ?? true;
455
+ this.enableCaching = config.enableCaching ?? true;
456
+ this.enableRateLimiting = config.enableRateLimiting ?? true;
457
+ }
458
+ async ensureLoaded() {
459
+ if (this.loaded)
460
+ return;
461
+ try {
462
+ const data = await fsp.readFile(this.filePath, 'utf8');
463
+ const parsed = JSON.parse(data);
464
+ for (const s of parsed)
465
+ this.servers.set(s.id, s);
466
+ }
467
+ catch (err) {
468
+ // If file doesn't exist, start empty
469
+ if (err.code !== 'ENOENT') {
470
+ // eslint-disable-next-line no-console
471
+ console.error('Failed to load managed servers:', err);
472
+ }
473
+ }
474
+ finally {
475
+ this.loaded = true;
476
+ }
477
+ }
478
+ async persist() {
479
+ const list = Array.from(this.servers.values());
480
+ await fsp.writeFile(this.filePath, JSON.stringify(list, null, 2), 'utf8');
481
+ }
482
+ async addServer(params) {
483
+ await this.ensureLoaded();
484
+ const server = {
485
+ id: uuidv4(),
486
+ ip: params.ip,
487
+ sourceDir: params.sourceDir,
488
+ name: params.name,
489
+ port: params.port,
490
+ createdAt: new Date().toISOString()
491
+ };
492
+ this.servers.set(server.id, server);
493
+ await this.persist();
494
+ return server;
495
+ }
496
+ async removeServer(id) {
497
+ await this.ensureLoaded();
498
+ const removed = this.servers.delete(id);
499
+ if (removed)
500
+ await this.persist();
501
+ return removed;
502
+ }
503
+ async listServers() {
504
+ await this.ensureLoaded();
505
+ return Array.from(this.servers.values());
506
+ }
507
+ async getServer(id) {
508
+ await this.ensureLoaded();
509
+ return this.servers.get(id);
510
+ }
511
+ async checkStatus(input) {
512
+ await this.ensureLoaded();
513
+ let ip = input.ip;
514
+ let sourceDir = input.sourceDir;
515
+ let id = input.id;
516
+ if (input.id) {
517
+ const found = this.servers.get(input.id);
518
+ if (!found) {
519
+ throw new Error(`Unknown managed server: ${input.id}`);
520
+ }
521
+ ip = found.ip;
522
+ sourceDir = found.sourceDir;
523
+ id = found.id;
524
+ }
525
+ if (!ip || !sourceDir) {
526
+ throw new Error('Must provide either id or both ip and sourceDir');
527
+ }
528
+ // Use custom port if available, otherwise default to common ports
529
+ let defaultPorts = [22, 80, 443];
530
+ if (input.id) {
531
+ const found = this.servers.get(input.id);
532
+ if (found?.port) {
533
+ defaultPorts = [found.port];
534
+ }
535
+ }
536
+ const ports = input.ports && input.ports.length > 0 ? input.ports : defaultPorts;
537
+ const byPort = {};
538
+ let usedCache = false;
539
+ let maxCacheAge = 0;
540
+ // Try TCP connect to each port with short timeout
541
+ await Promise.all(ports.map(async (port) => {
542
+ const { result, cached, cacheAgeMs } = await this.tcpCheckWithFeatures(ip, port, 1000, input.skipCache);
543
+ byPort[port] = result;
544
+ if (cached) {
545
+ usedCache = true;
546
+ maxCacheAge = Math.max(maxCacheAge, cacheAgeMs || 0);
547
+ }
548
+ }));
549
+ const online = Object.values(byPort).some(Boolean);
550
+ const sourceDirExists = fs.existsSync(sourceDir);
551
+ return {
552
+ id,
553
+ ip,
554
+ online,
555
+ ports: byPort,
556
+ sourceDir,
557
+ sourceDirExists,
558
+ checkedAt: new Date().toISOString(),
559
+ cached: usedCache ? true : undefined,
560
+ cacheAgeMs: usedCache ? maxCacheAge : undefined
561
+ };
562
+ }
563
+ /**
564
+ * TCP check with connection pooling, caching, and rate limiting
565
+ */
566
+ async tcpCheckWithFeatures(host, port, timeoutMs, skipCache) {
567
+ this.stats.totalChecks++;
568
+ // Check cache first (unless skipped)
569
+ if (this.enableCaching && !skipCache) {
570
+ const cached = this.healthCache.get(host, port);
571
+ if (cached !== null) {
572
+ this.stats.cacheHits++;
573
+ return { result: cached.result, cached: true, cacheAgeMs: cached.ageMs };
574
+ }
575
+ this.stats.cacheMisses++;
576
+ }
577
+ // Check rate limiting
578
+ if (this.enableRateLimiting) {
579
+ const waitTime = this.rateLimiter.isRateLimited(host, port);
580
+ if (waitTime > 0) {
581
+ this.stats.rateLimitedChecks++;
582
+ // Return false immediately for rate-limited endpoints
583
+ // Don't cache this - it's not a real check result
584
+ return { result: false, cached: false };
585
+ }
586
+ }
587
+ // Perform actual TCP check
588
+ let result;
589
+ if (this.enablePooling) {
590
+ result = await this.tcpCheckPooled(host, port, timeoutMs);
591
+ }
592
+ else {
593
+ result = await this.tcpCheckDirect(host, port, timeoutMs);
594
+ }
595
+ // Update rate limiter
596
+ if (this.enableRateLimiting) {
597
+ if (result) {
598
+ this.rateLimiter.recordSuccess(host, port);
599
+ }
600
+ else {
601
+ this.rateLimiter.recordFailure(host, port);
602
+ }
603
+ }
604
+ // Cache the result
605
+ if (this.enableCaching) {
606
+ this.healthCache.set(host, port, result);
607
+ }
608
+ return { result, cached: false };
609
+ }
610
+ /**
611
+ * TCP check using connection pool
612
+ */
613
+ async tcpCheckPooled(host, port, timeoutMs) {
614
+ try {
615
+ const { socket, reused } = await this.connectionPool.getConnection(host, port, timeoutMs);
616
+ if (reused) {
617
+ this.stats.pooledConnections++;
618
+ }
619
+ else {
620
+ this.stats.newConnections++;
621
+ }
622
+ // For health check, we just need to verify connection works
623
+ // Release connection back to pool (keep alive for reuse)
624
+ this.connectionPool.releaseConnection(host, port, socket, true);
625
+ return true;
626
+ }
627
+ catch {
628
+ return false;
629
+ }
630
+ }
631
+ /**
632
+ * Direct TCP check without pooling (original implementation)
633
+ */
634
+ tcpCheckDirect(host, port, timeoutMs) {
635
+ this.stats.newConnections++;
636
+ return new Promise((resolve) => {
637
+ const socket = new net.Socket();
638
+ let done = false;
639
+ const finalize = (result) => {
640
+ if (done)
641
+ return;
642
+ done = true;
643
+ try {
644
+ socket.destroy();
645
+ }
646
+ catch { /* noop */ }
647
+ resolve(result);
648
+ };
649
+ socket.setTimeout(timeoutMs);
650
+ socket.once('connect', () => finalize(true));
651
+ socket.once('timeout', () => finalize(false));
652
+ socket.once('error', () => finalize(false));
653
+ // Some hosts may refuse connection quickly - still indicates reachability
654
+ socket.once('close', (hadError) => finalize(!hadError));
655
+ try {
656
+ socket.connect(port, host);
657
+ }
658
+ catch {
659
+ finalize(false);
660
+ }
661
+ });
662
+ }
663
+ /**
664
+ * Get comprehensive statistics about TCP checks
665
+ */
666
+ getCheckStats() {
667
+ const totalConnections = this.stats.pooledConnections + this.stats.newConnections;
668
+ return {
669
+ ...this.stats,
670
+ cacheHitRate: this.stats.totalChecks > 0
671
+ ? this.stats.cacheHits / this.stats.totalChecks
672
+ : 0,
673
+ poolReuseRate: totalConnections > 0
674
+ ? this.stats.pooledConnections / totalConnections
675
+ : 0,
676
+ pool: this.connectionPool.getStats(),
677
+ cache: this.healthCache.getStats(),
678
+ rateLimit: this.rateLimiter.getStats()
679
+ };
680
+ }
681
+ /**
682
+ * Invalidate cache for a specific endpoint
683
+ */
684
+ invalidateCache(host, port) {
685
+ return this.healthCache.invalidate(host, port);
686
+ }
687
+ /**
688
+ * Clear all caches
689
+ */
690
+ clearCache() {
691
+ this.healthCache.clear();
692
+ }
693
+ /**
694
+ * Clear rate limiting for an endpoint
695
+ */
696
+ clearRateLimit(host, port) {
697
+ return this.rateLimiter.clear(host, port);
698
+ }
699
+ /**
700
+ * Clear all rate limiting
701
+ */
702
+ clearAllRateLimits() {
703
+ this.rateLimiter.clearAll();
704
+ }
705
+ /**
706
+ * Reset all statistics
707
+ */
708
+ resetStats() {
709
+ this.stats = {
710
+ totalChecks: 0,
711
+ cacheHits: 0,
712
+ cacheMisses: 0,
713
+ pooledConnections: 0,
714
+ newConnections: 0,
715
+ rateLimitedChecks: 0
716
+ };
717
+ }
718
+ /**
719
+ * Clean up resources (connection pool, timers)
720
+ */
721
+ destroy() {
722
+ this.connectionPool.destroy();
723
+ this.healthCache.clear();
724
+ this.rateLimiter.clearAll();
725
+ }
726
+ }
727
+ // Singleton default registry
728
+ export const managedServerRegistry = new ManagedServerRegistry();
729
+ //# sourceMappingURL=ManagedServerRegistry.js.map