@zintrust/workers 0.1.27

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 (178) hide show
  1. package/README.md +861 -0
  2. package/dist/AnomalyDetection.d.ts +102 -0
  3. package/dist/AnomalyDetection.js +321 -0
  4. package/dist/AutoScaler.d.ts +127 -0
  5. package/dist/AutoScaler.js +425 -0
  6. package/dist/BroadcastWorker.d.ts +21 -0
  7. package/dist/BroadcastWorker.js +24 -0
  8. package/dist/CanaryController.d.ts +103 -0
  9. package/dist/CanaryController.js +380 -0
  10. package/dist/ChaosEngineering.d.ts +79 -0
  11. package/dist/ChaosEngineering.js +216 -0
  12. package/dist/CircuitBreaker.d.ts +106 -0
  13. package/dist/CircuitBreaker.js +374 -0
  14. package/dist/ClusterLock.d.ts +90 -0
  15. package/dist/ClusterLock.js +385 -0
  16. package/dist/ComplianceManager.d.ts +177 -0
  17. package/dist/ComplianceManager.js +556 -0
  18. package/dist/DatacenterOrchestrator.d.ts +133 -0
  19. package/dist/DatacenterOrchestrator.js +404 -0
  20. package/dist/DeadLetterQueue.d.ts +122 -0
  21. package/dist/DeadLetterQueue.js +539 -0
  22. package/dist/HealthMonitor.d.ts +42 -0
  23. package/dist/HealthMonitor.js +301 -0
  24. package/dist/MultiQueueWorker.d.ts +89 -0
  25. package/dist/MultiQueueWorker.js +277 -0
  26. package/dist/NotificationWorker.d.ts +21 -0
  27. package/dist/NotificationWorker.js +23 -0
  28. package/dist/Observability.d.ts +153 -0
  29. package/dist/Observability.js +530 -0
  30. package/dist/PluginManager.d.ts +123 -0
  31. package/dist/PluginManager.js +392 -0
  32. package/dist/PriorityQueue.d.ts +117 -0
  33. package/dist/PriorityQueue.js +244 -0
  34. package/dist/ResourceMonitor.d.ts +164 -0
  35. package/dist/ResourceMonitor.js +605 -0
  36. package/dist/SLAMonitor.d.ts +110 -0
  37. package/dist/SLAMonitor.js +274 -0
  38. package/dist/WorkerFactory.d.ts +193 -0
  39. package/dist/WorkerFactory.js +1507 -0
  40. package/dist/WorkerInit.d.ts +85 -0
  41. package/dist/WorkerInit.js +223 -0
  42. package/dist/WorkerMetrics.d.ts +114 -0
  43. package/dist/WorkerMetrics.js +509 -0
  44. package/dist/WorkerRegistry.d.ts +145 -0
  45. package/dist/WorkerRegistry.js +319 -0
  46. package/dist/WorkerShutdown.d.ts +61 -0
  47. package/dist/WorkerShutdown.js +159 -0
  48. package/dist/WorkerVersioning.d.ts +107 -0
  49. package/dist/WorkerVersioning.js +300 -0
  50. package/dist/build-manifest.json +462 -0
  51. package/dist/config/workerConfig.d.ts +3 -0
  52. package/dist/config/workerConfig.js +19 -0
  53. package/dist/createQueueWorker.d.ts +23 -0
  54. package/dist/createQueueWorker.js +113 -0
  55. package/dist/dashboard/index.d.ts +1 -0
  56. package/dist/dashboard/index.js +1 -0
  57. package/dist/dashboard/types.d.ts +117 -0
  58. package/dist/dashboard/types.js +1 -0
  59. package/dist/dashboard/workers-api.d.ts +4 -0
  60. package/dist/dashboard/workers-api.js +638 -0
  61. package/dist/dashboard/workers-dashboard-ui.d.ts +3 -0
  62. package/dist/dashboard/workers-dashboard-ui.js +1026 -0
  63. package/dist/dashboard/workers-dashboard.d.ts +4 -0
  64. package/dist/dashboard/workers-dashboard.js +904 -0
  65. package/dist/helper/index.d.ts +5 -0
  66. package/dist/helper/index.js +10 -0
  67. package/dist/http/WorkerApiController.d.ts +38 -0
  68. package/dist/http/WorkerApiController.js +312 -0
  69. package/dist/http/WorkerController.d.ts +374 -0
  70. package/dist/http/WorkerController.js +1351 -0
  71. package/dist/http/middleware/CustomValidation.d.ts +92 -0
  72. package/dist/http/middleware/CustomValidation.js +270 -0
  73. package/dist/http/middleware/DatacenterValidator.d.ts +3 -0
  74. package/dist/http/middleware/DatacenterValidator.js +94 -0
  75. package/dist/http/middleware/EditWorkerValidation.d.ts +7 -0
  76. package/dist/http/middleware/EditWorkerValidation.js +55 -0
  77. package/dist/http/middleware/FeaturesValidator.d.ts +3 -0
  78. package/dist/http/middleware/FeaturesValidator.js +60 -0
  79. package/dist/http/middleware/InfrastructureValidator.d.ts +31 -0
  80. package/dist/http/middleware/InfrastructureValidator.js +226 -0
  81. package/dist/http/middleware/OptionsValidator.d.ts +3 -0
  82. package/dist/http/middleware/OptionsValidator.js +112 -0
  83. package/dist/http/middleware/PayloadSanitizer.d.ts +7 -0
  84. package/dist/http/middleware/PayloadSanitizer.js +42 -0
  85. package/dist/http/middleware/ProcessorPathSanitizer.d.ts +3 -0
  86. package/dist/http/middleware/ProcessorPathSanitizer.js +74 -0
  87. package/dist/http/middleware/QueueNameSanitizer.d.ts +3 -0
  88. package/dist/http/middleware/QueueNameSanitizer.js +45 -0
  89. package/dist/http/middleware/ValidateDriver.d.ts +7 -0
  90. package/dist/http/middleware/ValidateDriver.js +20 -0
  91. package/dist/http/middleware/VersionSanitizer.d.ts +3 -0
  92. package/dist/http/middleware/VersionSanitizer.js +25 -0
  93. package/dist/http/middleware/WorkerNameSanitizer.d.ts +3 -0
  94. package/dist/http/middleware/WorkerNameSanitizer.js +46 -0
  95. package/dist/http/middleware/WorkerValidationChain.d.ts +27 -0
  96. package/dist/http/middleware/WorkerValidationChain.js +185 -0
  97. package/dist/index.d.ts +46 -0
  98. package/dist/index.js +48 -0
  99. package/dist/routes/workers.d.ts +12 -0
  100. package/dist/routes/workers.js +81 -0
  101. package/dist/storage/WorkerStore.d.ts +45 -0
  102. package/dist/storage/WorkerStore.js +195 -0
  103. package/dist/type.d.ts +76 -0
  104. package/dist/type.js +1 -0
  105. package/dist/ui/router/ui.d.ts +3 -0
  106. package/dist/ui/router/ui.js +83 -0
  107. package/dist/ui/types/worker-ui.d.ts +229 -0
  108. package/dist/ui/types/worker-ui.js +5 -0
  109. package/package.json +53 -0
  110. package/src/AnomalyDetection.ts +434 -0
  111. package/src/AutoScaler.ts +654 -0
  112. package/src/BroadcastWorker.ts +34 -0
  113. package/src/CanaryController.ts +531 -0
  114. package/src/ChaosEngineering.ts +301 -0
  115. package/src/CircuitBreaker.ts +495 -0
  116. package/src/ClusterLock.ts +499 -0
  117. package/src/ComplianceManager.ts +815 -0
  118. package/src/DatacenterOrchestrator.ts +561 -0
  119. package/src/DeadLetterQueue.ts +733 -0
  120. package/src/HealthMonitor.ts +390 -0
  121. package/src/MultiQueueWorker.ts +431 -0
  122. package/src/NotificationWorker.ts +33 -0
  123. package/src/Observability.ts +696 -0
  124. package/src/PluginManager.ts +551 -0
  125. package/src/PriorityQueue.ts +351 -0
  126. package/src/ResourceMonitor.ts +769 -0
  127. package/src/SLAMonitor.ts +408 -0
  128. package/src/WorkerFactory.ts +2108 -0
  129. package/src/WorkerInit.ts +313 -0
  130. package/src/WorkerMetrics.ts +709 -0
  131. package/src/WorkerRegistry.ts +443 -0
  132. package/src/WorkerShutdown.ts +210 -0
  133. package/src/WorkerVersioning.ts +422 -0
  134. package/src/config/workerConfig.ts +25 -0
  135. package/src/createQueueWorker.ts +174 -0
  136. package/src/dashboard/index.ts +6 -0
  137. package/src/dashboard/types.ts +141 -0
  138. package/src/dashboard/workers-api.ts +785 -0
  139. package/src/dashboard/zintrust.svg +30 -0
  140. package/src/helper/index.ts +11 -0
  141. package/src/http/WorkerApiController.ts +369 -0
  142. package/src/http/WorkerController.ts +1512 -0
  143. package/src/http/middleware/CustomValidation.ts +360 -0
  144. package/src/http/middleware/DatacenterValidator.ts +124 -0
  145. package/src/http/middleware/EditWorkerValidation.ts +74 -0
  146. package/src/http/middleware/FeaturesValidator.ts +82 -0
  147. package/src/http/middleware/InfrastructureValidator.ts +295 -0
  148. package/src/http/middleware/OptionsValidator.ts +144 -0
  149. package/src/http/middleware/PayloadSanitizer.ts +52 -0
  150. package/src/http/middleware/ProcessorPathSanitizer.ts +86 -0
  151. package/src/http/middleware/QueueNameSanitizer.ts +55 -0
  152. package/src/http/middleware/ValidateDriver.ts +29 -0
  153. package/src/http/middleware/VersionSanitizer.ts +30 -0
  154. package/src/http/middleware/WorkerNameSanitizer.ts +56 -0
  155. package/src/http/middleware/WorkerValidationChain.ts +230 -0
  156. package/src/index.ts +98 -0
  157. package/src/routes/workers.ts +154 -0
  158. package/src/storage/WorkerStore.ts +240 -0
  159. package/src/type.ts +89 -0
  160. package/src/types/queue-monitor.d.ts +38 -0
  161. package/src/types/queue-redis.d.ts +38 -0
  162. package/src/ui/README.md +13 -0
  163. package/src/ui/components/JsonEditor.js +670 -0
  164. package/src/ui/components/JsonViewer.js +387 -0
  165. package/src/ui/components/WorkerCard.js +178 -0
  166. package/src/ui/components/WorkerExpandPanel.js +257 -0
  167. package/src/ui/components/fetcher.js +42 -0
  168. package/src/ui/components/sla-scorecard.js +32 -0
  169. package/src/ui/components/styles.css +30 -0
  170. package/src/ui/components/table-expander.js +34 -0
  171. package/src/ui/integration/worker-ui-integration.js +565 -0
  172. package/src/ui/router/ui.ts +99 -0
  173. package/src/ui/services/workerApi.js +240 -0
  174. package/src/ui/types/worker-ui.ts +283 -0
  175. package/src/ui/utils/jsonValidator.js +444 -0
  176. package/src/ui/workers/index.html +202 -0
  177. package/src/ui/workers/main.js +1781 -0
  178. package/src/ui/workers/styles.css +1350 -0
@@ -0,0 +1,499 @@
1
+ /**
2
+ * Cluster Lock Manager
3
+ * Distributed locking using Redis for multi-instance worker coordination
4
+ * Sealed namespace for immutability
5
+ */
6
+
7
+ import {
8
+ ErrorFactory,
9
+ Logger,
10
+ createRedisConnection,
11
+ generateUuid,
12
+ type RedisConfig,
13
+ } from '@zintrust/core';
14
+ import type IORedis from 'ioredis';
15
+
16
+ export type LockAcquisitionOptions = {
17
+ lockKey: string;
18
+ ttl: number; // Time-to-live in seconds
19
+ region?: string;
20
+ userId?: string;
21
+ };
22
+
23
+ export type LockInfo = {
24
+ lockKey: string;
25
+ instanceId: string;
26
+ acquiredAt: Date;
27
+ expiresAt: Date;
28
+ region: string;
29
+ userId?: string;
30
+ };
31
+
32
+ export type AuditLogEntry = {
33
+ timestamp: Date;
34
+ operation: 'acquire' | 'release' | 'extend' | 'force-release';
35
+ lockKey: string;
36
+ instanceId: string;
37
+ userId?: string;
38
+ reason?: string;
39
+ success: boolean;
40
+ };
41
+
42
+ // Generate unique instance ID for this process
43
+ const INSTANCE_ID = `worker-${process.pid}-${Date.now()}-${generateUuid()}`;
44
+
45
+ // Redis key prefixes
46
+ const LOCK_PREFIX = 'worker:lock:';
47
+ const AUDIT_PREFIX = 'worker:audit:lock:';
48
+
49
+ // Internal state
50
+ let redisClient: IORedis | null = null;
51
+ let heartbeatInterval: NodeJS.Timeout | null = null;
52
+ const activeLocks = new Map<string, LockInfo>();
53
+
54
+ /**
55
+ * Helper: Get full Redis key for lock
56
+ */
57
+ const getLockKey = (lockKey: string): string => {
58
+ return `${LOCK_PREFIX}${lockKey}`;
59
+ };
60
+
61
+ /**
62
+ * Helper: Get full Redis key for audit log
63
+ */
64
+ const getAuditKey = (lockKey: string): string => {
65
+ return `${AUDIT_PREFIX}${lockKey}`;
66
+ };
67
+
68
+ /**
69
+ * Helper: Store audit log entry in Redis
70
+ */
71
+ const auditLockOperation = async (client: IORedis, entry: AuditLogEntry): Promise<void> => {
72
+ try {
73
+ const auditKey = getAuditKey(entry.lockKey);
74
+ const auditData = JSON.stringify(entry);
75
+
76
+ // Store in sorted set with timestamp as score for easy retrieval
77
+ await client.zadd(auditKey, entry.timestamp.getTime(), auditData);
78
+
79
+ // Keep only last 1000 entries per lock
80
+ await client.zremrangebyrank(auditKey, 0, -1001);
81
+
82
+ // Expire audit logs after 30 days
83
+ await client.expire(auditKey, 30 * 24 * 60 * 60);
84
+ } catch (error) {
85
+ Logger.error('Failed to write lock audit log', error);
86
+ // Don't throw - audit failure shouldn't break lock operations
87
+ }
88
+ };
89
+
90
+ /**
91
+ * Helper: Extend lock TTL
92
+ */
93
+ const extendLockTTL = async (client: IORedis, lockKey: string, ttl: number): Promise<boolean> => {
94
+ const redisKey = getLockKey(lockKey);
95
+ const value = await client.get(redisKey);
96
+
97
+ if (value === null || value !== INSTANCE_ID) {
98
+ return false; // Lock not held by this instance
99
+ }
100
+
101
+ const result = await client.expire(redisKey, ttl);
102
+ return result === 1;
103
+ };
104
+
105
+ /**
106
+ * Helper: Start heartbeat for lock extension
107
+ */
108
+ const startHeartbeat = (client: IORedis): void => {
109
+ if (heartbeatInterval) {
110
+ return; // Already running
111
+ }
112
+
113
+ heartbeatInterval = setInterval(async () => {
114
+ const lockEntries = Array.from(activeLocks.entries());
115
+
116
+ await Promise.allSettled(
117
+ lockEntries.map(async ([lockKey, info]) => {
118
+ try {
119
+ const now = new Date();
120
+ const timeUntilExpiry = info.expiresAt.getTime() - now.getTime();
121
+
122
+ // Extend if less than 30 seconds until expiry
123
+ if (timeUntilExpiry < 30000) {
124
+ const ttl = Math.ceil(timeUntilExpiry / 1000) + 60; // Extend by 60 more seconds
125
+ const extended = await extendLockTTL(client, lockKey, ttl);
126
+
127
+ if (extended) {
128
+ info.expiresAt = new Date(now.getTime() + ttl * 1000);
129
+ Logger.debug(`Extended lock "${lockKey}" TTL to ${ttl}s`);
130
+
131
+ await auditLockOperation(client, {
132
+ timestamp: now,
133
+ operation: 'extend',
134
+ lockKey,
135
+ instanceId: INSTANCE_ID,
136
+ success: true,
137
+ });
138
+ } else {
139
+ // Lost the lock
140
+ activeLocks.delete(lockKey);
141
+ Logger.warn(`Lost lock "${lockKey}" - it was released or taken by another instance`);
142
+ }
143
+ }
144
+ } catch (error) {
145
+ Logger.error(`Failed to extend lock "${lockKey}"`, error);
146
+ }
147
+ })
148
+ );
149
+ }, 10000); // Check every 10 seconds
150
+
151
+ Logger.debug('Lock heartbeat started');
152
+ };
153
+
154
+ /**
155
+ * Helper: Stop heartbeat
156
+ */
157
+ const stopHeartbeat = (): void => {
158
+ if (heartbeatInterval) {
159
+ clearInterval(heartbeatInterval);
160
+ heartbeatInterval = null;
161
+ Logger.debug('Lock heartbeat stopped');
162
+ }
163
+ };
164
+
165
+ /**
166
+ * Cluster Lock Manager - Sealed namespace
167
+ */
168
+ export const ClusterLock = Object.freeze({
169
+ /**
170
+ * Initialize the lock manager with Redis connection
171
+ */
172
+ initialize(config: RedisConfig): void {
173
+ if (redisClient) {
174
+ Logger.warn('ClusterLock already initialized');
175
+ return;
176
+ }
177
+
178
+ redisClient = createRedisConnection(config);
179
+ startHeartbeat(redisClient);
180
+
181
+ Logger.info('ClusterLock initialized', { instanceId: INSTANCE_ID });
182
+ },
183
+
184
+ /**
185
+ * Acquire a distributed lock
186
+ */
187
+ async acquire(options: LockAcquisitionOptions): Promise<boolean> {
188
+ if (!redisClient) {
189
+ throw ErrorFactory.createGeneralError(
190
+ 'ClusterLock not initialized. Call initialize() first.'
191
+ );
192
+ }
193
+
194
+ const { lockKey, ttl, region = 'default', userId } = options;
195
+ const redisKey = getLockKey(lockKey);
196
+ const now = new Date();
197
+
198
+ try {
199
+ // Try to acquire lock using SET NX EX (set if not exists with expiry)
200
+ const result = await redisClient.set(redisKey, INSTANCE_ID, 'EX', ttl, 'NX');
201
+
202
+ const success = result === 'OK';
203
+
204
+ if (success) {
205
+ const lockInfo: LockInfo = {
206
+ lockKey,
207
+ instanceId: INSTANCE_ID,
208
+ acquiredAt: now,
209
+ expiresAt: new Date(now.getTime() + ttl * 1000),
210
+ region,
211
+ userId,
212
+ };
213
+
214
+ activeLocks.set(lockKey, lockInfo);
215
+
216
+ Logger.info(`Acquired lock "${lockKey}"`, {
217
+ region,
218
+ userId,
219
+ ttl,
220
+ expiresAt: lockInfo.expiresAt.toISOString(),
221
+ });
222
+
223
+ await auditLockOperation(redisClient, {
224
+ timestamp: now,
225
+ operation: 'acquire',
226
+ lockKey,
227
+ instanceId: INSTANCE_ID,
228
+ userId,
229
+ success: true,
230
+ });
231
+ } else {
232
+ Logger.debug(`Failed to acquire lock "${lockKey}" - already held by another instance`);
233
+
234
+ await auditLockOperation(redisClient, {
235
+ timestamp: now,
236
+ operation: 'acquire',
237
+ lockKey,
238
+ instanceId: INSTANCE_ID,
239
+ userId,
240
+ success: false,
241
+ });
242
+ }
243
+
244
+ return success;
245
+ } catch (error) {
246
+ Logger.error(`Error acquiring lock "${lockKey}"`, error);
247
+ throw error;
248
+ }
249
+ },
250
+
251
+ /**
252
+ * Release a distributed lock
253
+ */
254
+ async release(lockKey: string, userId?: string): Promise<boolean> {
255
+ if (!redisClient) {
256
+ throw ErrorFactory.createGeneralError('ClusterLock not initialized');
257
+ }
258
+
259
+ const redisKey = getLockKey(lockKey);
260
+ const now = new Date();
261
+
262
+ try {
263
+ // Only release if we own the lock
264
+ const value = await redisClient.get(redisKey);
265
+
266
+ if (value !== INSTANCE_ID) {
267
+ Logger.warn(`Cannot release lock "${lockKey}" - not owned by this instance`);
268
+ return false;
269
+ }
270
+
271
+ await redisClient.del(redisKey);
272
+ activeLocks.delete(lockKey);
273
+
274
+ Logger.info(`Released lock "${lockKey}"`, { userId });
275
+
276
+ await auditLockOperation(redisClient, {
277
+ timestamp: now,
278
+ operation: 'release',
279
+ lockKey,
280
+ instanceId: INSTANCE_ID,
281
+ userId,
282
+ success: true,
283
+ });
284
+
285
+ return true;
286
+ } catch (error) {
287
+ Logger.error(`Error releasing lock "${lockKey}"`, error);
288
+ throw error;
289
+ }
290
+ },
291
+
292
+ /**
293
+ * Extend lock TTL
294
+ */
295
+ async extend(lockKey: string, ttl: number): Promise<boolean> {
296
+ if (!redisClient) {
297
+ throw ErrorFactory.createGeneralError('ClusterLock not initialized');
298
+ }
299
+
300
+ const extended = await extendLockTTL(redisClient, lockKey, ttl);
301
+
302
+ if (extended) {
303
+ const info = activeLocks.get(lockKey);
304
+ if (info) {
305
+ info.expiresAt = new Date(Date.now() + ttl * 1000);
306
+ }
307
+ Logger.debug(`Extended lock "${lockKey}" TTL to ${ttl}s`);
308
+ }
309
+
310
+ return extended;
311
+ },
312
+
313
+ /**
314
+ * Check if lock is held by this instance
315
+ */
316
+ async isHeldByMe(lockKey: string): Promise<boolean> {
317
+ if (!redisClient) {
318
+ return false;
319
+ }
320
+
321
+ const redisKey = getLockKey(lockKey);
322
+ const value = await redisClient.get(redisKey);
323
+
324
+ return value === INSTANCE_ID;
325
+ },
326
+
327
+ /**
328
+ * Force release a lock (admin operation)
329
+ */
330
+ async forceRelease(lockKey: string, userId: string, reason: string): Promise<boolean> {
331
+ if (!redisClient) {
332
+ throw ErrorFactory.createGeneralError('ClusterLock not initialized');
333
+ }
334
+
335
+ const redisKey = getLockKey(lockKey);
336
+ const now = new Date();
337
+
338
+ try {
339
+ const currentOwner = await redisClient.get(redisKey);
340
+
341
+ if (currentOwner === null) {
342
+ Logger.warn(`Lock "${lockKey}" does not exist`);
343
+ return false;
344
+ }
345
+
346
+ await redisClient.del(redisKey);
347
+
348
+ // Remove from active locks if we owned it
349
+ if (currentOwner === INSTANCE_ID) {
350
+ activeLocks.delete(lockKey);
351
+ }
352
+
353
+ Logger.warn(`Force released lock "${lockKey}"`, {
354
+ userId,
355
+ reason,
356
+ previousOwner: currentOwner,
357
+ });
358
+
359
+ await auditLockOperation(redisClient, {
360
+ timestamp: now,
361
+ operation: 'force-release',
362
+ lockKey,
363
+ instanceId: currentOwner,
364
+ userId,
365
+ reason,
366
+ success: true,
367
+ });
368
+
369
+ return true;
370
+ } catch (error) {
371
+ Logger.error(`Error force releasing lock "${lockKey}"`, error);
372
+ throw error;
373
+ }
374
+ },
375
+
376
+ /**
377
+ * List all locks
378
+ */
379
+ async listLocks(): Promise<ReadonlyArray<{ key: string; owner: string; region?: string }>> {
380
+ if (!redisClient) {
381
+ throw ErrorFactory.createGeneralError('ClusterLock not initialized');
382
+ }
383
+
384
+ try {
385
+ const pattern = `${LOCK_PREFIX}*`;
386
+ const keys = await redisClient.keys(pattern);
387
+
388
+ const locks = await Promise.all(
389
+ keys.map(async (key) => {
390
+ const owner = await redisClient?.get(key);
391
+ const lockKey = key.replace(LOCK_PREFIX, '');
392
+ const info = activeLocks.get(lockKey);
393
+
394
+ return {
395
+ key: lockKey,
396
+ owner: owner ?? 'unknown',
397
+ region: info?.region,
398
+ };
399
+ })
400
+ );
401
+
402
+ return locks;
403
+ } catch (error) {
404
+ Logger.error('Error listing locks', error);
405
+ throw error;
406
+ }
407
+ },
408
+
409
+ /**
410
+ * Get lock owner
411
+ */
412
+ async getLockOwner(lockKey: string): Promise<string | null> {
413
+ if (!redisClient) {
414
+ throw ErrorFactory.createGeneralError('ClusterLock not initialized');
415
+ }
416
+
417
+ const redisKey = getLockKey(lockKey);
418
+ return redisClient.get(redisKey);
419
+ },
420
+
421
+ /**
422
+ * Get locks by region
423
+ */
424
+ getLocksByRegion(region: string): ReadonlyArray<LockInfo> {
425
+ const locks: LockInfo[] = [];
426
+
427
+ for (const info of activeLocks.values()) {
428
+ if (info.region === region) {
429
+ locks.push({ ...info });
430
+ }
431
+ }
432
+
433
+ return locks;
434
+ },
435
+
436
+ /**
437
+ * Get audit log for a lock
438
+ */
439
+ async getAuditLog(lockKey: string, limit = 100): Promise<ReadonlyArray<AuditLogEntry>> {
440
+ if (!redisClient) {
441
+ throw ErrorFactory.createGeneralError('ClusterLock not initialized');
442
+ }
443
+
444
+ try {
445
+ const auditKey = getAuditKey(lockKey);
446
+
447
+ // Get latest entries (highest scores = most recent timestamps)
448
+ const entries = await redisClient.zrevrange(auditKey, 0, limit - 1);
449
+
450
+ return entries.map((entry) => JSON.parse(entry) as AuditLogEntry);
451
+ } catch (error) {
452
+ Logger.error(`Error retrieving audit log for "${lockKey}"`, error);
453
+ return [];
454
+ }
455
+ },
456
+
457
+ /**
458
+ * Get active locks held by this instance
459
+ */
460
+ getActiveLocks(): ReadonlyArray<LockInfo> {
461
+ return Array.from(activeLocks.values()).map((info) => ({ ...info }));
462
+ },
463
+
464
+ /**
465
+ * Get instance ID
466
+ */
467
+ getInstanceId(): string {
468
+ return INSTANCE_ID;
469
+ },
470
+
471
+ /**
472
+ * Shutdown and release all locks
473
+ */
474
+ async shutdown(): Promise<void> {
475
+ if (!redisClient) {
476
+ return;
477
+ }
478
+
479
+ Logger.info('ClusterLock shutting down...');
480
+
481
+ stopHeartbeat();
482
+
483
+ // Release all active locks
484
+ const releasePromises = Array.from(activeLocks.keys()).map(async (lockKey) =>
485
+ ClusterLock.release(lockKey, 'system-shutdown')
486
+ );
487
+
488
+ await Promise.all(releasePromises);
489
+
490
+ if (redisClient !== null) {
491
+ await redisClient.quit();
492
+ redisClient = null;
493
+ }
494
+
495
+ Logger.info('ClusterLock shutdown complete');
496
+ },
497
+ });
498
+
499
+ // Graceful shutdown handled by WorkerShutdown